add prune and remove unused packages

This commit is contained in:
Michelle Au
2019-03-08 14:54:43 -08:00
parent f59b58d164
commit 8c0accad66
17240 changed files with 27 additions and 4750030 deletions

View File

@@ -1,31 +0,0 @@
# Contributing Guidelines
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
## Getting Started
We have full documentation on how to get started contributing here:
<!---
If your repo has certain guidelines for contribution, put them here ahead of the general k8s resources
-->
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet.md) - Common resources for existing developers
## Mentorship
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
<!---
Custom Information - if you're copying this template for the first time you can add custom content here, for example:
## Contact Information
- [Slack channel](https://kubernetes.slack.com/messages/kubernetes-users) - Replace `kubernetes-users` with your slack channel string, this will send users directly to your channel.
- [Mailing list](URL)
-->

View File

@@ -1,165 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "NUT"
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
version = "v1.1.1"
[[projects]]
digest = "1:bff0ce7c8e3d6357fa5a8549bbe4bdb620bddc13c11ae569aa7248ea92e2139f"
name = "github.com/golang/protobuf"
packages = [
"descriptor",
"proto",
"protoc-gen-go/descriptor",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp",
"ptypes/wrappers",
]
pruneopts = "NUT"
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
version = "v1.2.0"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "NUT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
digest = "1:0331452965d8695c0a5633e0f509012987681a654f388f2ad0c3b2d7f7004b1c"
name = "github.com/stretchr/testify"
packages = [
"assert",
"require",
]
pruneopts = "NUT"
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
branch = "master"
digest = "1:dc5dcbb306af980306fab456d4788e88c880177227bd488a92d5bef7d8743497"
name = "golang.org/x/net"
packages = [
"context",
"http/httpguts",
"http2",
"http2/hpack",
"idna",
"internal/timeseries",
"trace",
]
pruneopts = "NUT"
revision = "fae4c4e3ad76c295c3d6d259f898136b4bf833a8"
[[projects]]
branch = "master"
digest = "1:134392bed1074be912a37ea13483f27e6e827f4e4d6cc319f69d3ac3617d5ac0"
name = "golang.org/x/sys"
packages = ["unix"]
pruneopts = "NUT"
revision = "4ed8d59d0b35e1e29334a206d1b3f38b1e5dfb31"
[[projects]]
digest = "1:e7071ed636b5422cc51c0e3a6cebc229d6c9fffc528814b519a980641422d619"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
]
pruneopts = "NUT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
branch = "master"
digest = "1:56b0bca90b7e5d1facf5fbdacba23e4e0ce069d25381b8e2f70ef1e7ebfb9c1a"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
pruneopts = "NUT"
revision = "31ac5d88444a9e7ad18077db9a165d793ad06a2e"
[[projects]]
digest = "1:2f91d3e11b666570f8c923912f1cc8cf2f0c6b7371b2687ee67a8f73f08c6272"
name = "google.golang.org/grpc"
packages = [
".",
"balancer",
"balancer/base",
"balancer/roundrobin",
"codes",
"connectivity",
"credentials",
"encoding",
"encoding/proto",
"grpclog",
"internal",
"internal/backoff",
"internal/channelz",
"internal/envconfig",
"internal/grpcrand",
"internal/transport",
"keepalive",
"metadata",
"naming",
"peer",
"resolver",
"resolver/dns",
"resolver/passthrough",
"stats",
"status",
"tap",
]
pruneopts = "NUT"
revision = "2e463a05d100327ca47ac218281906921038fd95"
version = "v1.16.0"
[[projects]]
digest = "1:9cc257b3c9ff6a0158c9c661ab6eebda1fe8a4a4453cd5c4044dc9a2ebfb992b"
name = "k8s.io/klog"
packages = ["."]
pruneopts = "NUT"
revision = "a5bc97fbc634d635061f3146511332c7e313a55a"
version = "v0.1.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/golang/protobuf/descriptor",
"github.com/golang/protobuf/proto",
"github.com/golang/protobuf/protoc-gen-go/descriptor",
"github.com/golang/protobuf/ptypes/timestamp",
"github.com/golang/protobuf/ptypes/wrappers",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/require",
"golang.org/x/net/context",
"google.golang.org/grpc",
"google.golang.org/grpc/codes",
"google.golang.org/grpc/connectivity",
"google.golang.org/grpc/status",
"k8s.io/klog",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -1,7 +0,0 @@
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
[prune]
go-tests = true
non-go = true
unused-packages = true

View File

@@ -1,20 +0,0 @@
# Copyright 2018 The 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.
# No individual commands at the moment, just packages.
CMDS=
all:
go build `go list ./... | grep -v 'vendor'`
include release-tools/build.make

View File

@@ -1,7 +0,0 @@
# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md
approvers:
- saad-ali
- jsafrane
- msau42
- pohly

View File

@@ -1,19 +0,0 @@
# csi-lib-utils
TBD
## Community, discussion, contribution, and support
Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/).
You can reach the maintainers of this project at:
- [Slack](http://slack.k8s.io/)
- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-dev)
### Code of conduct
Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md).
[owners]: https://git.k8s.io/community/contributors/guide/owners.md
[Creative Commons 4.0]: https://git.k8s.io/website/LICENSE

View File

@@ -1,13 +0,0 @@
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Product Security Team to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://kubernetes.io/security/
saad-ali

View File

@@ -1,3 +0,0 @@
# Kubernetes Community Code of Conduct
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)

View File

@@ -1,308 +0,0 @@
/*
Copyright 2019 The 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 connection
import (
"context"
"io/ioutil"
"net"
"os"
"path"
"sync"
"testing"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/status"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func tmpDir(t *testing.T) string {
dir, err := ioutil.TempDir("", "connect")
require.NoError(t, err, "creating temp directory")
return dir
}
const (
serverSock = "server.sock"
)
// startServer creates a gRPC server without any registered services.
// The returned address can be used to connect to it. The cleanup
// function stops it. It can be called multiple times.
func startServer(t *testing.T, tmp string) (string, func()) {
addr := path.Join(tmp, serverSock)
listener, err := net.Listen("unix", addr)
require.NoError(t, err, "listening on %s", addr)
server := grpc.NewServer()
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
if err := server.Serve(listener); err != nil {
t.Logf("starting server failed: %s", err)
}
}()
return addr, func() {
server.Stop()
wg.Wait()
if err := os.Remove(addr); err != nil && !os.IsNotExist(err) {
t.Logf("remove Unix socket: %s", err)
}
}
}
func TestConnect(t *testing.T) {
tmp := tmpDir(t)
defer os.RemoveAll(tmp)
addr, stopServer := startServer(t, tmp)
defer stopServer()
conn, err := Connect(addr)
if assert.NoError(t, err, "connect via absolute path") &&
assert.NotNil(t, conn, "got a connection") {
assert.Equal(t, connectivity.Ready, conn.GetState(), "connection ready")
err = conn.Close()
assert.NoError(t, err, "closing connection")
}
}
func TestConnectUnix(t *testing.T) {
tmp := tmpDir(t)
defer os.RemoveAll(tmp)
addr, stopServer := startServer(t, tmp)
defer stopServer()
conn, err := Connect("unix:///" + addr)
if assert.NoError(t, err, "connect with unix:/// prefix") &&
assert.NotNil(t, conn, "got a connection") {
assert.Equal(t, connectivity.Ready, conn.GetState(), "connection ready")
err = conn.Close()
assert.NoError(t, err, "closing connection")
}
}
func TestWaitForServer(t *testing.T) {
tmp := tmpDir(t)
defer os.RemoveAll(tmp)
// We cannot test that Connect() waits forever for the server
// to appear, because then we would have to let the test run
// forever.... What we can test is that it returns shortly
// after the server appears.
startTime := time.Now()
var startTimeServer time.Time
var stopServer func()
var wg sync.WaitGroup
wg.Add(1)
defer func() {
wg.Wait()
stopServer()
}()
// Here we pick a relatively long delay before we start the
// server. If gRPC did go into an exponential backoff before
// retrying the connection attempt, then it probably would
// not react promptly to the server becoming ready. Currently
// it looks like gRPC tries to connect once per second, with
// no exponential backoff.
delay := 10 * time.Second
go func() {
defer wg.Done()
t.Logf("sleeping %s before starting server", delay)
time.Sleep(delay)
startTimeServer = time.Now()
_, stopServer = startServer(t, tmp)
}()
conn, err := Connect(path.Join(tmp, serverSock))
if assert.NoError(t, err, "connect via absolute path") {
endTime := time.Now()
assert.NotNil(t, conn, "got a connection")
assert.Equal(t, connectivity.Ready.String(), conn.GetState().String(), "connection ready")
if assert.InEpsilon(t, 1*time.Second, endTime.Sub(startTimeServer), 5, "connection established shortly after server starts") {
assert.InEpsilon(t, delay, endTime.Sub(startTime), 1)
}
err = conn.Close()
assert.NoError(t, err, "closing connection")
}
}
func TestTimout(t *testing.T) {
tmp := tmpDir(t)
defer os.RemoveAll(tmp)
startTime := time.Now()
timeout := 5 * time.Second
conn, err := connect(path.Join(tmp, "no-such.sock"), []grpc.DialOption{grpc.WithTimeout(timeout)}, nil)
endTime := time.Now()
if assert.Error(t, err, "connection should fail") {
assert.InEpsilon(t, timeout, endTime.Sub(startTime), 1, "connection timeout")
} else {
err := conn.Close()
assert.NoError(t, err, "closing connection")
}
}
func TestReconnect(t *testing.T) {
tmp := tmpDir(t)
defer os.RemoveAll(tmp)
addr, stopServer := startServer(t, tmp)
defer func() {
stopServer()
}()
// Allow reconnection (the default).
conn, err := Connect(addr)
if assert.NoError(t, err, "connect via absolute path") &&
assert.NotNil(t, conn, "got a connection") {
defer conn.Close()
assert.Equal(t, connectivity.Ready, conn.GetState(), "connection ready")
if err := conn.Invoke(context.Background(), "/connect.v0.Test/Ping", nil, nil); assert.Error(t, err) {
errStatus, _ := status.FromError(err)
assert.Equal(t, codes.Unimplemented, errStatus.Code(), "not implemented")
}
stopServer()
startTime := time.Now()
if err := conn.Invoke(context.Background(), "/connect.v0.Test/Ping", nil, nil); assert.Error(t, err) {
endTime := time.Now()
errStatus, _ := status.FromError(err)
assert.Equal(t, codes.Unavailable, errStatus.Code(), "connection lost")
assert.InEpsilon(t, time.Second, endTime.Sub(startTime), 1, "connection loss should be detected quickly")
}
// No reconnection either when the server comes back.
_, stopServer = startServer(t, tmp)
// We need to give gRPC some time. It does not attempt to reconnect
// immediately. If we send the method call too soon, the test passes
// even though a later method call will go through again.
time.Sleep(5 * time.Second)
startTime = time.Now()
if err := conn.Invoke(context.Background(), "/connect.v0.Test/Ping", nil, nil); assert.Error(t, err) {
endTime := time.Now()
errStatus, _ := status.FromError(err)
assert.Equal(t, codes.Unimplemented, errStatus.Code(), "not implemented")
assert.InEpsilon(t, time.Second, endTime.Sub(startTime), 1, "connection loss should be covered from quickly")
}
}
}
func TestDisconnect(t *testing.T) {
tmp := tmpDir(t)
defer os.RemoveAll(tmp)
addr, stopServer := startServer(t, tmp)
defer func() {
stopServer()
}()
reconnectCount := 0
conn, err := Connect(addr, OnConnectionLoss(func() bool {
reconnectCount++
// Don't reconnect.
return false
}))
if assert.NoError(t, err, "connect via absolute path") &&
assert.NotNil(t, conn, "got a connection") {
defer conn.Close()
assert.Equal(t, connectivity.Ready, conn.GetState(), "connection ready")
if err := conn.Invoke(context.Background(), "/connect.v0.Test/Ping", nil, nil); assert.Error(t, err) {
errStatus, _ := status.FromError(err)
assert.Equal(t, codes.Unimplemented, errStatus.Code(), "not implemented")
}
stopServer()
startTime := time.Now()
if err := conn.Invoke(context.Background(), "/connect.v0.Test/Ping", nil, nil); assert.Error(t, err) {
endTime := time.Now()
errStatus, _ := status.FromError(err)
assert.Equal(t, codes.Unavailable, errStatus.Code(), "connection lost")
assert.InEpsilon(t, time.Second, endTime.Sub(startTime), 1, "connection loss should be detected quickly")
}
// No reconnection either when the server comes back.
_, stopServer = startServer(t, tmp)
// We need to give gRPC some time. It does not attempt to reconnect
// immediately. If we send the method call too soon, the test passes
// even though a later method call will go through again.
time.Sleep(5 * time.Second)
startTime = time.Now()
if err := conn.Invoke(context.Background(), "/connect.v0.Test/Ping", nil, nil); assert.Error(t, err) {
endTime := time.Now()
errStatus, _ := status.FromError(err)
assert.Equal(t, codes.Unavailable, errStatus.Code(), "connection still lost")
assert.InEpsilon(t, time.Second, endTime.Sub(startTime), 1, "connection loss should be detected quickly")
}
assert.Equal(t, 1, reconnectCount, "connection loss callback should be called once")
}
}
func TestExplicitReconnect(t *testing.T) {
tmp := tmpDir(t)
defer os.RemoveAll(tmp)
addr, stopServer := startServer(t, tmp)
defer func() {
stopServer()
}()
reconnectCount := 0
conn, err := Connect(addr, OnConnectionLoss(func() bool {
reconnectCount++
// Reconnect.
return true
}))
if assert.NoError(t, err, "connect via absolute path") &&
assert.NotNil(t, conn, "got a connection") {
defer conn.Close()
assert.Equal(t, connectivity.Ready, conn.GetState(), "connection ready")
if err := conn.Invoke(context.Background(), "/connect.v0.Test/Ping", nil, nil); assert.Error(t, err) {
errStatus, _ := status.FromError(err)
assert.Equal(t, codes.Unimplemented, errStatus.Code(), "not implemented")
}
stopServer()
startTime := time.Now()
if err := conn.Invoke(context.Background(), "/connect.v0.Test/Ping", nil, nil); assert.Error(t, err) {
endTime := time.Now()
errStatus, _ := status.FromError(err)
assert.Equal(t, codes.Unavailable, errStatus.Code(), "connection lost")
assert.InEpsilon(t, time.Second, endTime.Sub(startTime), 1, "connection loss should be detected quickly")
}
// No reconnection either when the server comes back.
_, stopServer = startServer(t, tmp)
// We need to give gRPC some time. It does not attempt to reconnect
// immediately. If we send the method call too soon, the test passes
// even though a later method call will go through again.
time.Sleep(5 * time.Second)
startTime = time.Now()
if err := conn.Invoke(context.Background(), "/connect.v0.Test/Ping", nil, nil); assert.Error(t, err) {
endTime := time.Now()
errStatus, _ := status.FromError(err)
assert.Equal(t, codes.Unimplemented, errStatus.Code(), "connection still lost")
assert.InEpsilon(t, time.Second, endTime.Sub(startTime), 1, "connection loss should be recovered from quickly")
}
assert.Equal(t, 1, reconnectCount, "connection loss callback should be called once")
}
}

View File

@@ -1,224 +0,0 @@
/*
Copyright 2018 The 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 protosanitizer
import (
"fmt"
"testing"
"github.com/golang/protobuf/proto"
csi03 "github.com/kubernetes-csi/csi-lib-utils/protosanitizer/test/csi03"
csi "github.com/kubernetes-csi/csi-lib-utils/protosanitizer/test/csi10"
"github.com/kubernetes-csi/csi-lib-utils/protosanitizer/test/csitest"
"github.com/stretchr/testify/assert"
)
func TestStripSecrets(t *testing.T) {
secretName := "secret-abc"
secretValue := "123"
// CSI 0.3.0.
createVolumeCSI03 := &csi03.CreateVolumeRequest{
AccessibilityRequirements: &csi03.TopologyRequirement{
Requisite: []*csi03.Topology{
&csi03.Topology{
Segments: map[string]string{
"foo": "bar",
"x": "y",
},
},
&csi03.Topology{
Segments: map[string]string{
"a": "b",
},
},
},
},
Name: "foo",
VolumeCapabilities: []*csi03.VolumeCapability{
&csi03.VolumeCapability{
AccessType: &csi03.VolumeCapability_Mount{
Mount: &csi03.VolumeCapability_MountVolume{
FsType: "ext4",
},
},
},
},
CapacityRange: &csi03.CapacityRange{
RequiredBytes: 1024,
},
ControllerCreateSecrets: map[string]string{
secretName: secretValue,
"secret-xyz": "987",
},
}
// Current spec.
createVolume := &csi.CreateVolumeRequest{
AccessibilityRequirements: &csi.TopologyRequirement{
Requisite: []*csi.Topology{
&csi.Topology{
Segments: map[string]string{
"foo": "bar",
"x": "y",
},
},
&csi.Topology{
Segments: map[string]string{
"a": "b",
},
},
},
},
Name: "foo",
VolumeCapabilities: []*csi.VolumeCapability{
&csi.VolumeCapability{
AccessType: &csi.VolumeCapability_Mount{
Mount: &csi.VolumeCapability_MountVolume{
FsType: "ext4",
},
},
},
},
CapacityRange: &csi.CapacityRange{
RequiredBytes: 1024,
},
Secrets: map[string]string{
secretName: secretValue,
"secret-xyz": "987",
},
}
// Revised spec with more secret fields.
createVolumeFuture := &csitest.CreateVolumeRequest{
CapacityRange: &csitest.CapacityRange{
RequiredBytes: 1024,
},
MaybeSecretMap: map[int64]*csitest.VolumeCapability{
1: &csitest.VolumeCapability{ArraySecret: "aaa"},
2: &csitest.VolumeCapability{ArraySecret: "bbb"},
},
Name: "foo",
NewSecretInt: 42,
Seecreets: map[string]string{
secretName: secretValue,
"secret-xyz": "987",
},
VolumeCapabilities: []*csitest.VolumeCapability{
&csitest.VolumeCapability{
AccessType: &csitest.VolumeCapability_Mount{
Mount: &csitest.VolumeCapability_MountVolume{
FsType: "ext4",
},
},
ArraySecret: "knock knock",
},
&csitest.VolumeCapability{
ArraySecret: "Who's there?",
},
},
VolumeContentSource: &csitest.VolumeContentSource{
Type: &csitest.VolumeContentSource_Volume{
Volume: &csitest.VolumeContentSource_VolumeSource{
VolumeId: "abc",
OneofSecretField: "hello",
},
},
NestedSecretField: "world",
},
}
type testcase struct {
original, stripped interface{}
}
cases := []testcase{
{nil, "null"},
{1, "1"},
{"hello world", `"hello world"`},
{true, "true"},
{false, "false"},
{&csi.CreateVolumeRequest{}, `{}`},
// Test case from https://github.com/kubernetes-csi/csi-lib-utils/pull/1#pullrequestreview-180126394.
{&csi.CreateVolumeRequest{
Name: "test-volume",
CapacityRange: &csi.CapacityRange{
RequiredBytes: int64(1024),
LimitBytes: int64(1024),
},
VolumeCapabilities: []*csi.VolumeCapability{
&csi.VolumeCapability{
AccessType: &csi.VolumeCapability_Mount{
Mount: &csi.VolumeCapability_MountVolume{
FsType: "ext4",
MountFlags: []string{"flag1", "flag2", "flag3"},
},
},
AccessMode: &csi.VolumeCapability_AccessMode{
Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
},
},
},
Secrets: map[string]string{"secret1": "secret1", "secret2": "secret2"},
Parameters: map[string]string{"param1": "param1", "param2": "param2"},
VolumeContentSource: &csi.VolumeContentSource{},
AccessibilityRequirements: &csi.TopologyRequirement{},
}, `{"accessibility_requirements":{},"capacity_range":{"limit_bytes":1024,"required_bytes":1024},"name":"test-volume","parameters":{"param1":"param1","param2":"param2"},"secrets":"***stripped***","volume_capabilities":[{"AccessType":{"Mount":{"fs_type":"ext4","mount_flags":["flag1","flag2","flag3"]}},"access_mode":{"mode":5}}],"volume_content_source":{"Type":null}}`},
{createVolume, `{"accessibility_requirements":{"requisite":[{"segments":{"foo":"bar","x":"y"}},{"segments":{"a":"b"}}]},"capacity_range":{"required_bytes":1024},"name":"foo","secrets":"***stripped***","volume_capabilities":[{"AccessType":{"Mount":{"fs_type":"ext4"}}}]}`},
{createVolumeCSI03, `{"accessibility_requirements":{"requisite":[{"segments":{"foo":"bar","x":"y"}},{"segments":{"a":"b"}}]},"capacity_range":{"required_bytes":1024},"controller_create_secrets":"***stripped***","name":"foo","volume_capabilities":[{"AccessType":{"Mount":{"fs_type":"ext4"}}}]}`},
{&csitest.CreateVolumeRequest{}, `{}`},
{createVolumeFuture,
// Secrets are *not* removed from all fields yet. This will have to be fixed one way or another
// before the CSI spec can start using secrets there (currently it doesn't).
// The test is still useful because it shows that also complicated fields get serialized.
// `{"capacity_range":{"required_bytes":1024},"maybe_secret_map":{"1":{"AccessType":null,"array_secret":"***stripped***"},"2":{"AccessType":null,"array_secret":"***stripped***"}},"name":"foo","new_secret_int":"***stripped***","seecreets":"***stripped***","volume_capabilities":[{"AccessType":{"Mount":{"fs_type":"ext4"}},"array_secret":"***stripped***"},{"AccessType":null,"array_secret":"***stripped***"}],"volume_content_source":{"Type":{"Volume":{"oneof_secret_field":"***stripped***","volume_id":"abc"}},"nested_secret_field":"***stripped***"}}`,
`{"capacity_range":{"required_bytes":1024},"maybe_secret_map":{"1":{"AccessType":null,"array_secret":"aaa"},"2":{"AccessType":null,"array_secret":"bbb"}},"name":"foo","new_secret_int":"***stripped***","seecreets":"***stripped***","volume_capabilities":[{"AccessType":{"Mount":{"fs_type":"ext4"}},"array_secret":"***stripped***"},{"AccessType":null,"array_secret":"***stripped***"}],"volume_content_source":{"Type":{"Volume":{"oneof_secret_field":"hello","volume_id":"abc"}},"nested_secret_field":"***stripped***"}}`,
},
}
// Message from revised spec as received by a sidecar based on the current spec.
// The XXX_unrecognized field contains secrets and must not get logged.
unknownFields := &csi.CreateVolumeRequest{}
data, err := proto.Marshal(createVolumeFuture)
if assert.NoError(t, err, "marshall future message") &&
assert.NoError(t, proto.Unmarshal(data, unknownFields), "unmarshal with unknown fields") {
cases = append(cases, testcase{unknownFields,
`{"capacity_range":{"required_bytes":1024},"name":"foo","secrets":"***stripped***","volume_capabilities":[{"AccessType":{"Mount":{"fs_type":"ext4"}}},{"AccessType":null}],"volume_content_source":{"Type":{"Volume":{"volume_id":"abc"}}}}`,
})
}
for _, c := range cases {
before := fmt.Sprint(c.original)
var stripped fmt.Stringer
if _, ok := c.original.(*csi03.CreateVolumeRequest); ok {
stripped = StripSecretsCSI03(c.original)
} else {
stripped = StripSecrets(c.original)
}
if assert.Equal(t, c.stripped, fmt.Sprintf("%s", stripped), "unexpected result for fmt s of %s", c.original) {
if assert.Equal(t, c.stripped, fmt.Sprintf("%v", stripped), "unexpected result for fmt v of %s", c.original) {
assert.Equal(t, c.stripped, fmt.Sprintf("%+v", stripped), "unexpected result for fmt +v of %s", c.original)
}
}
assert.Equal(t, before, fmt.Sprint(c.original), "original value modified")
}
// The secret is hidden because StripSecrets is a struct referencing it.
dump := fmt.Sprintf("%#v", StripSecrets(createVolume))
assert.NotContains(t, dump, secretName)
assert.NotContains(t, dump, secretValue)
}

View File

@@ -1,5 +0,0 @@
/protoc
/protoc-gen-go
/csi.a
/.protoc
.build

View File

@@ -1,136 +0,0 @@
all: build
########################################################################
## GOLANG ##
########################################################################
# If GOPATH isn't defined then set its default location.
ifeq (,$(strip $(GOPATH)))
GOPATH := $(HOME)/go
else
# If GOPATH is already set then update GOPATH to be its own
# first element.
GOPATH := $(word 1,$(subst :, ,$(GOPATH)))
endif
export GOPATH
########################################################################
## PROTOC ##
########################################################################
# Only set PROTOC_VER if it has an empty value.
ifeq (,$(strip $(PROTOC_VER)))
PROTOC_VER := 3.5.1
endif
PROTOC_OS := $(shell uname -s)
ifeq (Darwin,$(PROTOC_OS))
PROTOC_OS := osx
endif
PROTOC_ARCH := $(shell uname -m)
ifeq (i386,$(PROTOC_ARCH))
PROTOC_ARCH := x86_32
endif
PROTOC := ./protoc
PROTOC_ZIP := protoc-$(PROTOC_VER)-$(PROTOC_OS)-$(PROTOC_ARCH).zip
PROTOC_URL := https://github.com/google/protobuf/releases/download/v$(PROTOC_VER)/$(PROTOC_ZIP)
PROTOC_TMP_DIR := .protoc
PROTOC_TMP_BIN := $(PROTOC_TMP_DIR)/bin/protoc
$(PROTOC):
-mkdir -p "$(PROTOC_TMP_DIR)" && \
curl -L $(PROTOC_URL) -o "$(PROTOC_TMP_DIR)/$(PROTOC_ZIP)" && \
unzip "$(PROTOC_TMP_DIR)/$(PROTOC_ZIP)" -d "$(PROTOC_TMP_DIR)" && \
chmod 0755 "$(PROTOC_TMP_BIN)" && \
cp -f "$(PROTOC_TMP_BIN)" "$@"
stat "$@" > /dev/null 2>&1
########################################################################
## PROTOC-GEN-GO ##
########################################################################
# This is the recipe for getting and installing the go plug-in
# for protoc
PROTOC_GEN_GO_PKG := github.com/golang/protobuf/protoc-gen-go
PROTOC_GEN_GO := protoc-gen-go
$(PROTOC_GEN_GO): PROTOBUF_PKG := $(dir $(PROTOC_GEN_GO_PKG))
$(PROTOC_GEN_GO): PROTOBUF_VERSION := v1.2.0
$(PROTOC_GEN_GO):
mkdir -p $(dir $(GOPATH)/src/$(PROTOBUF_PKG))
test -d $(GOPATH)/src/$(PROTOBUF_PKG)/.git || git clone https://$(PROTOBUF_PKG) $(GOPATH)/src/$(PROTOBUF_PKG)
(cd $(GOPATH)/src/$(PROTOBUF_PKG) && \
(test "$$(git describe --tags | head -1)" = "$(PROTOBUF_VERSION)" || \
(git fetch && git checkout tags/$(PROTOBUF_VERSION))))
(cd $(GOPATH)/src/$(PROTOBUF_PKG) && go get -v -d $$(go list -f '{{ .ImportPath }}' ./...)) && \
go build -o "$@" $(PROTOC_GEN_GO_PKG)
########################################################################
## PATH ##
########################################################################
# Update PATH with the current directory. This enables the protoc
# binary to discover the protoc-gen-go binary, built inside this
# directory.
export PATH := $(shell pwd):$(PATH)
########################################################################
## BUILD ##
########################################################################
CSI_PROTO := ./csitest.proto
CSI_PKG_ROOT := github.com/kubernetes-csi/csi-lib-utils/protosanitizer/test
CSI_PKG_SUB := $(shell cat $(CSI_PROTO) | sed -n -e 's/^package.\([^;]*\).v[0-9]\+;$$/\1/p'|tr '.' '/')
CSI_BUILD := $(CSI_PKG_SUB)/.build
CSI_GO := $(CSI_PKG_SUB)/csitest.pb.go
CSI_A := csi.a
CSI_GO_TMP := $(CSI_BUILD)/$(CSI_PKG_ROOT)/csitest.pb.go
# This recipe generates the go language bindings to a temp area.
$(CSI_GO_TMP): HERE := $(shell pwd)
$(CSI_GO_TMP): PTYPES_PKG := github.com/golang/protobuf/ptypes
$(CSI_GO_TMP): GO_OUT := plugins=grpc
$(CSI_GO_TMP): GO_OUT := $(GO_OUT),Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor
$(CSI_GO_TMP): GO_OUT := $(GO_OUT),Mgoogle/protobuf/wrappers.proto=$(PTYPES_PKG)/wrappers
$(CSI_GO_TMP): GO_OUT := $(GO_OUT):"$(HERE)/$(CSI_BUILD)"
$(CSI_GO_TMP): INCLUDE := -I$(GOPATH)/src -I$(HERE)/$(PROTOC_TMP_DIR)/include
$(CSI_GO_TMP): $(CSI_PROTO) | $(PROTOC) $(PROTOC_GEN_GO)
@mkdir -p "$(@D)"
(cd "$(GOPATH)/src" && \
$(HERE)/$(PROTOC) $(INCLUDE) --go_out=$(GO_OUT) "$(CSI_PKG_ROOT)/$(<F)")
# The temp language bindings are compared to the ones that are
# versioned. If they are different then it means the language
# bindings were not updated prior to being committed.
$(CSI_GO): $(CSI_GO_TMP)
ifeq (true,$(TRAVIS))
diff "$@" "$?"
else
@mkdir -p "$(@D)"
diff "$@" "$?" > /dev/null 2>&1 || cp -f "$?" "$@"
endif
# This recipe builds the Go archive from the sources in three steps:
#
# 1. Go get any missing dependencies.
# 2. Cache the packages.
# 3. Build the archive file.
$(CSI_A): $(CSI_GO)
go get -v -d ./...
go install ./$(CSI_PKG_SUB)
go build -o "$@" ./$(CSI_PKG_SUB)
build: $(CSI_A)
clean:
go clean -i ./...
rm -rf "$(CSI_A)" "$(CSI_GO)" "$(CSI_BUILD)"
clobber: clean
rm -fr "$(PROTOC)" "$(PROTOC_GEN_GO)" "$(CSI_PKG_SUB)" "$(PROTOC_TMP_DIR)"
.PHONY: clean clobber

View File

@@ -1,2 +0,0 @@
This is a *modified* version of the CSI 1.0.0 spec. It's only purpose is
to test the stripping of secret fields.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +0,0 @@
# Contributing Guidelines
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
## Getting Started
We have full documentation on how to get started contributing here:
<!---
If your repo has certain guidelines for contribution, put them here ahead of the general k8s resources
-->
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet.md) - Common resources for existing developers
## Mentorship
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
<!---
Custom Information - if you're copying this template for the first time you can add custom content here, for example:
## Contact Information
- [Slack channel](https://kubernetes.slack.com/messages/kubernetes-users) - Replace `kubernetes-users` with your slack channel string, this will send users directly to your channel.
- [Mailing list](URL)
-->

View File

@@ -1,11 +0,0 @@
# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md
approvers:
- saad-ali
- msau42
- pohly
reviewers:
- saad-ali
- msau42
- pohly

View File

@@ -1,51 +0,0 @@
# [csi-release-tools](https://github.com/kubernetes-csi/csi-release-tools)
These build and test rules can be shared between different Go projects
without modifications. Customization for the different projects happen
in the top-level Makefile.
The rules include support for building and pushing Docker images, with
the following features:
- one or more command and image per project
- push canary and/or tagged release images
- automatically derive the image tag(s) from repo tags
- the source code revision is stored in a "revision" image label
- never overwrites an existing release image
Usage
-----
The expected repository layout is:
- `cmd/*/*.go` - source code for each command
- `cmd/*/Dockerfile` - docker file for each command or
Dockerfile in the root when only building a single command
- `Makefile` - includes `release-tools/build.make` and sets
configuration variables
- `.travis.yml` - a symlink to `release-tools/.travis.yml`
To create a release, tag a certain revision with a name that
starts with `v`, for example `v1.0.0`, then `make push`
while that commit is checked out.
It does not matter on which branch that revision exists, i.e. it is
possible to create releases directly from master. A release branch can
still be created for maintenance releases later if needed.
Release branches are expected to be named `release-x.y` for releases
`x.y.z`. Building from such a branch creates `x.y-canary`
images. Building from master creates the main `canary` image.
Sharing and updating
--------------------
[`git subtree`](https://github.com/git/git/blob/master/contrib/subtree/git-subtree.txt)
is the recommended way of maintaining a copy of the rules inside the
`release-tools` directory of a project. This way, it is possible to make
changes also locally, test them and then push them back to the shared
repository at a later time.
Cheat sheet:
- `git subtree add --prefix=release-tools https://github.com/kubernetes-csi/csi-release-tools.git master` - add release tools to a repo which does not have them yet (only once)
- `git subtree pull --prefix=release-tools https://github.com/kubernetes-csi/csi-release-tools.git master` - update local copy to latest upstream (whenever upstream changes)
- edit, `git commit`, `git subtree push --prefix=release-tools git@github.com:<user>/csi-release-tools.git <my-new-or-existing-branch>` - push to a new branch before submitting a PR

View File

@@ -1,5 +0,0 @@
# Release Process
No tagged releases are planned at this point. The intention is to keep
the master branch in a state such that it can be used for all
supported branches in downstream repos which use these files.

View File

@@ -1,14 +0,0 @@
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Product Security Team to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://kubernetes.io/security/
saad-ali
msau42

View File

@@ -1,124 +0,0 @@
# Copyright 2019 The 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.
.PHONY: build-% build container-% container push-% push clean test
# A space-separated list of all commands in the repository, must be
# set in main Makefile of a repository.
# CMDS=
# This is the default. It can be overridden in the main Makefile after
# including build.make.
REGISTRY_NAME=quay.io/k8scsi
# Revision that gets built into each binary via the main.version
# string. Uses the `git describe` output based on the most recent
# version tag with a short revision suffix or, if nothing has been
# tagged yet, just the revision.
#
# Beware that tags may also be missing in shallow clones as done by
# some CI systems (like TravisCI, which pulls only 50 commits).
REV=$(shell git describe --long --tags --match='v*' --dirty 2>/dev/null || git rev-list -n1 HEAD)
# A space-separated list of image tags under which the current build is to be pushed.
# Determined dynamically.
IMAGE_TAGS=
# A "canary" image gets built if the current commit is the head of the remote "master" branch.
# That branch does not exist when building some other branch in TravisCI.
IMAGE_TAGS+=$(shell if [ "$$(git rev-list -n1 HEAD)" = "$$(git rev-list -n1 origin/master 2>/dev/null)" ]; then echo "canary"; fi)
# A "X.Y.Z-canary" image gets built if the current commit is the head of a "origin/release-X.Y.Z" branch.
# The actual suffix does not matter, only the "release-" prefix is checked.
IMAGE_TAGS+=$(shell git branch -r --points-at=HEAD | grep 'origin/release-' | grep -v -e ' -> ' | sed -e 's;.*/release-\(.*\);\1-canary;')
# A release image "vX.Y.Z" gets built if there is a tag of that format for the current commit.
# --abbrev=0 suppresses long format, only showing the closest tag.
IMAGE_TAGS+=$(shell tagged="$$(git describe --tags --match='v*' --abbrev=0)"; if [ "$$tagged" ] && [ "$$(git rev-list -n1 HEAD)" = "$$(git rev-list -n1 $$tagged)" ]; then echo $$tagged; fi)
# Images are named after the command contained in them.
IMAGE_NAME=$(REGISTRY_NAME)/$*
ifdef V
# Adding "-alsologtostderr" assumes that all test binaries contain glog. This is not guaranteed.
TESTARGS = -v -args -alsologtostderr -v 5
else
TESTARGS =
endif
# Specific packages can be excluded from each of the tests below by setting the *_FILTER_CMD variables
# to something like "| grep -v 'github.com/kubernetes-csi/project/pkg/foobar'". See usage below.
build-%:
mkdir -p bin
CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-X main.version=$(REV) -extldflags "-static"' -o ./bin/$* ./cmd/$*
container-%: build-%
docker build -t $*:latest -f $(shell if [ -e ./cmd/$*/Dockerfile ]; then echo ./cmd/$*/Dockerfile; else echo Dockerfile; fi) --label revision=$(REV) .
push-%: container-%
set -ex; \
push_image () { \
docker tag $*:latest $(IMAGE_NAME):$$tag; \
docker push $(IMAGE_NAME):$$tag; \
}; \
for tag in $(IMAGE_TAGS); do \
if [ "$$tag" = "canary" ] || echo "$$tag" | grep -q -e '-canary$$'; then \
: "creating or overwriting canary image"; \
push_image; \
elif docker pull $(IMAGE_NAME):$$tag 2>&1 | tee /dev/stderr | grep -q "manifest for $(IMAGE_NAME):$$tag not found"; then \
: "creating release image"; \
push_image; \
else \
: "release image $(IMAGE_NAME):$$tag already exists, skipping push"; \
fi; \
done
build: $(CMDS:%=build-%)
container: $(CMDS:%=container-%)
push: $(CMDS:%=push-%)
clean:
-rm -rf bin
test:
.PHONY: test-go
test: test-go
test-go:
@ echo; echo "### $@:"
go test `go list ./... | grep -v 'vendor' $(TEST_GO_FILTER_CMD)` $(TESTARGS)
.PHONY: test-vet
test: test-vet
test-vet:
@ echo; echo "### $@:"
go vet `go list ./... | grep -v vendor $(TEST_VET_FILTER_CMD)`
.PHONY: test-fmt
test: test-fmt
test-fmt:
@ echo; echo "### $@:"
files=$$(find . -name '*.go' | grep -v './vendor' $(TEST_FMT_FILTER_CMD)); \
if [ $$(gofmt -d $$files | wc -l) -ne 0 ]; then \
echo "formatting errors:"; \
gofmt -d $$files; \
false; \
fi
.PHONY: test-subtree
test: test-subtree
test-subtree:
@ echo; echo "### $@:"
./release-tools/verify-subtree.sh release-tools

View File

@@ -1,3 +0,0 @@
# Kubernetes Community Code of Conduct
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)

View File

@@ -1,14 +0,0 @@
language: go
sudo: required
services:
- docker
matrix:
include:
- go: 1.11.1
script:
- make -k all test
after_success:
- if [ "${TRAVIS_PULL_REQUEST}" == "false" ]; then
docker login -u "${DOCKER_USERNAME}" -p "${DOCKER_PASSWORD}" quay.io;
make push;
fi

View File

@@ -1,41 +0,0 @@
#! /bin/sh -e
#
# Copyright 2019 The 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.
# This script verifies that the content of a directory managed
# by "git subtree" has not been modified locally. It does that
# by looking for commits that modify the files with the
# subtree prefix (aka directory) while ignoring merge
# commits. Merge commits are where "git subtree" pulls the
# upstream files into the directory.
#
# Theoretically a developer can subvert this check by modifying files
# in a merge commit, but in practice that shouldn't happen.
DIR="$1"
if [ ! "$DIR" ]; then
echo "usage: $0 <directory>" >&2
exit 1
fi
REV=$(git log -n1 --format=format:%H --no-merges -- "$DIR")
if [ "$REV" ]; then
echo "Directory '$DIR' contains non-upstream changes:"
echo
git log --no-merges -- "$DIR"
exit 1
else
echo "$DIR is a clean copy of upstream."
fi

View File

@@ -1,19 +0,0 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
bin/mock
cmd/csi-sanity/csi-sanity
# JetBrains GoLand
.idea
# Vim
*.swp

View File

@@ -1,15 +0,0 @@
language: go
sudo: required
services:
- docker
matrix:
include:
- go: 1.10.3
script:
- make test
after_success:
- if [ "${TRAVIS_BRANCH}" == "master" ] && [ "${TRAVIS_PULL_REQUEST}" == "false" ]; then
make container
docker login -u "${DOCKER_USERNAME}" -p "${DOCKER_PASSWORD}" quay.io;
make push;
fi

View File

@@ -1,22 +0,0 @@
# Contributing Guidelines
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). Here is an excerpt:
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
## Getting Started
We have full documentation on how to get started contributing here:
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet.md) - Common resources for existing developers
## Mentorship
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
## Contact Information
- [Slack channel](https://kubernetes.slack.com/messages/sig-storage)
- [Mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-storage)

View File

@@ -1,6 +0,0 @@
FROM alpine
LABEL maintainers="Kubernetes Authors"
LABEL description="CSI Mock Driver"
COPY ./bin/mock mock
ENTRYPOINT ["/mock"]

View File

@@ -1,237 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:26ee2356254e58b9872ba736f66aff1c54a26f08c7d16afbf49695131a87d454"
name = "github.com/container-storage-interface/spec"
packages = ["lib/go/csi"]
pruneopts = "UT"
revision = "8efcc85c45550571fba8134182013ed7dc34038a"
version = "v1.0.0-rc2"
[[projects]]
digest = "1:bc38c7c481812e178d85160472e231c5e1c9a7f5845d67e23ee4e706933c10d8"
name = "github.com/golang/mock"
packages = ["gomock"]
pruneopts = "UT"
revision = "c34cdb4725f4c3844d095133c6e40e448b86589b"
version = "v1.1.1"
[[projects]]
digest = "1:588beb9f80d2b0afddf05663b32d01c867da419458b560471d81cca0286e76b8"
name = "github.com/golang/protobuf"
packages = [
"proto",
"protoc-gen-go/descriptor",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp",
"ptypes/wrappers",
]
pruneopts = "UT"
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
version = "v1.2.0"
[[projects]]
digest = "1:72f35d3e412bc67b121e15ea4c88a3b3da8bcbc2264339e7ffa4a1865799840c"
name = "github.com/onsi/ginkgo"
packages = [
".",
"config",
"internal/codelocation",
"internal/containernode",
"internal/failer",
"internal/leafnodes",
"internal/remote",
"internal/spec",
"internal/spec_iterator",
"internal/specrunner",
"internal/suite",
"internal/testingtproxy",
"internal/writer",
"reporters",
"reporters/stenographer",
"reporters/stenographer/support/go-colorable",
"reporters/stenographer/support/go-isatty",
"types",
]
pruneopts = "UT"
revision = "fa5fabab2a1bfbd924faf4c067d07ae414e2aedf"
version = "v1.5.0"
[[projects]]
digest = "1:d0c2c4e2d0006cd28c220a549cda1de8e67abc65ed4c572421492bbf0492ceaf"
name = "github.com/onsi/gomega"
packages = [
".",
"format",
"internal/assertion",
"internal/asyncassertion",
"internal/oraclematcher",
"internal/testingtsupport",
"matchers",
"matchers/support/goraph/bipartitegraph",
"matchers/support/goraph/edge",
"matchers/support/goraph/node",
"matchers/support/goraph/util",
"types",
]
pruneopts = "UT"
revision = "62bff4df71bdbc266561a0caee19f0594b17c240"
version = "v1.4.0"
[[projects]]
digest = "1:9e9193aa51197513b3abcb108970d831fbcf40ef96aa845c4f03276e1fa316d2"
name = "github.com/sirupsen/logrus"
packages = ["."]
pruneopts = "UT"
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
[[projects]]
branch = "master"
digest = "1:3f3a05ae0b95893d90b9b3b5afdb79a9b3d96e4e36e099d841ae602e4aca0da8"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
pruneopts = "UT"
revision = "8ac0e0d97ce45cd83d1d7243c060cb8461dda5e9"
[[projects]]
branch = "master"
digest = "1:0bb2e6ef036484991ed446a6c698698b8901766981d4d22cc8e53fedb09709ac"
name = "golang.org/x/net"
packages = [
"context",
"html",
"html/atom",
"html/charset",
"http/httpguts",
"http2",
"http2/hpack",
"idna",
"internal/timeseries",
"trace",
]
pruneopts = "UT"
revision = "1e491301e022f8f977054da4c2d852decd59571f"
[[projects]]
branch = "master"
digest = "1:8fbfc6ea1a8a078697633be97f07dd83a83d32a96959d42195464c13c25be374"
name = "golang.org/x/sys"
packages = [
"unix",
"windows",
]
pruneopts = "UT"
revision = "9527bec2660bd847c050fda93a0f0c6dee0800bb"
[[projects]]
digest = "1:436b24586f8fee329e0dd65fd67c817681420cda1d7f934345c13fe78c212a73"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"encoding",
"encoding/charmap",
"encoding/htmlindex",
"encoding/internal",
"encoding/internal/identifier",
"encoding/japanese",
"encoding/korean",
"encoding/simplifiedchinese",
"encoding/traditionalchinese",
"encoding/unicode",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"internal/utf8internal",
"language",
"runes",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
]
pruneopts = "UT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
branch = "master"
digest = "1:601e63e7d4577f907118bec825902505291918859d223bce015539e79f1160e3"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
pruneopts = "UT"
revision = "32ee49c4dd805befd833990acba36cb75042378c"
[[projects]]
digest = "1:7a977fdcd5abff03e94f92e7b374ef37e91c7c389581e5c4348fa98616e6c6be"
name = "google.golang.org/grpc"
packages = [
".",
"balancer",
"balancer/base",
"balancer/roundrobin",
"channelz",
"codes",
"connectivity",
"credentials",
"encoding",
"encoding/proto",
"grpclb/grpc_lb_v1/messages",
"grpclog",
"internal",
"keepalive",
"metadata",
"naming",
"peer",
"reflection",
"reflection/grpc_reflection_v1alpha",
"resolver",
"resolver/dns",
"resolver/passthrough",
"stats",
"status",
"tap",
"transport",
]
pruneopts = "UT"
revision = "7a6a684ca69eb4cae85ad0a484f2e531598c047b"
version = "v1.12.2"
[[projects]]
digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "UT"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/container-storage-interface/spec/lib/go/csi",
"github.com/golang/mock/gomock",
"github.com/golang/protobuf/proto",
"github.com/golang/protobuf/ptypes",
"github.com/golang/protobuf/ptypes/wrappers",
"github.com/onsi/ginkgo",
"github.com/onsi/gomega",
"github.com/sirupsen/logrus",
"golang.org/x/net/context",
"google.golang.org/grpc",
"google.golang.org/grpc/codes",
"google.golang.org/grpc/connectivity",
"google.golang.org/grpc/reflection",
"google.golang.org/grpc/status",
"gopkg.in/yaml.v2",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -1,62 +0,0 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/container-storage-interface/spec"
version = "v1.0.0-rc2"
[[constraint]]
name = "github.com/golang/mock"
version = "1.0.0"
[[constraint]]
name = "github.com/golang/protobuf"
version = "v1.2.0"
[[constraint]]
name = "github.com/onsi/ginkgo"
version = "1.4.0"
[[constraint]]
name = "github.com/onsi/gomega"
version = "1.3.0"
[[constraint]]
branch = "master"
name = "golang.org/x/net"
[[constraint]]
name = "google.golang.org/grpc"
version = "1.9.2"
[[constraint]]
name = "gopkg.in/yaml.v2"
version = "v2.1.1"
[prune]
go-tests = true
unused-packages = true

View File

@@ -1,52 +0,0 @@
# Copyright 2018 The 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.
IMAGE_NAME = quay.io/k8scsi/mock-driver
IMAGE_VERSION = canary
APP := ./bin/mock
ifdef V
TESTARGS = -v -args -alsologtostderr -v 5
else
TESTARGS =
endif
all: $(APP)
$(APP):
mkdir -p bin
CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o $(APP) ./mock/main.go
clean:
rm -rf bin
container: $(APP)
docker build -f Dockerfile.mock -t $(IMAGE_NAME):$(IMAGE_VERSION) .
push: container
docker push $(IMAGE_NAME):$(IMAGE_VERSION)
test: $(APP)
files=$$(find ./ -name '*.go' | grep -v '^./vendor' ); \
if [ $$(gofmt -d $$files | wc -l) -ne 0 ]; then \
echo "formatting errors:"; \
gofmt -d $$files; \
false; \
fi
go vet $$(go list ./... | grep -v vendor)
go test $$(go list ./... | grep -v vendor | grep -v "cmd/csi-sanity")
./hack/e2e.sh
.PHONY: all clean container push test

View File

@@ -1,4 +0,0 @@
approvers:
- saad-ali
- lpabon
- pohly

View File

@@ -1,42 +0,0 @@
[![Build Status](https://travis-ci.org/kubernetes-csi/csi-test.svg?branch=master)](https://travis-ci.org/kubernetes-csi/csi-test)
[![Docker Repository on Quay](https://quay.io/repository/k8scsi/mock-driver/status "Docker Repository on
Quay")](https://quay.io/repository/k8scsi/mock-driver)
# csi-test
csi-test houses packages and libraries to help test CSI client and plugins.
## For Container Orchestration Tests
CO developers can use this framework to create drivers based on the
[Golang mock](https://github.com/golang/mock) framework. Please see
[co_test.go](test/co_test.go) for an example.
### Mock driver for testing
We also provide a container called `quay.io/k8scsi/mock-driver:canary` which can be used as an in-memory mock driver.
It follows the same release cycle as other containers, so the latest release is `quay.io/k8scsi/mock-driver:v0.3.0`.
You will need to setup the environment variable `CSI_ENDPOINT` for the mock driver to know where to create the unix
domain socket.
## For CSI Driver Tests
To test drivers please take a look at [pkg/sanity](https://github.com/kubernetes-csi/csi-test/tree/master/pkg/sanity).
This package and [csi-sanity](https://github.com/kubernetes-csi/csi-test/tree/master/cmd/csi-sanity) are meant to test
the CSI API capability of a driver. They are meant to be an additional test to the unit, functional, and e2e tests of a
CSI driver.
### Note
* Master is for CSI v0.4.0. Please see the branches for other CSI releases.
* Only Golang 1.9+ supported. See [gRPC issue](https://github.com/grpc/grpc-go/issues/711#issuecomment-326626790)
## Community, discussion, contribution, and support
Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/).
You can reach the maintainers of this project at:
- [Slack channel](https://kubernetes.slack.com/messages/sig-storage)
- [Mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-storage)
### Code of conduct
Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md).

View File

@@ -1,14 +0,0 @@
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Product Security Team to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://kubernetes.io/security/
saad-ali
lpabon

View File

@@ -1,61 +0,0 @@
APP_NAME := csi-sanity
VER :=$(shell git describe)
RELEASEVER := $(shell git describe --abbrev=0)
BRANCH := $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD))
SHA := $(shell git rev-parse --short HEAD)
ARCH := $(shell go env GOARCH)
GOOS := $(shell go env GOOS)
DIR=.
ifdef APP_SUFFIX
VERSION = $(VER)-$(subst /,-,$(APP_SUFFIX))
else
ifeq (master,$(BRANCH))
VERSION = $(VER)
else
VERSION = $(VER)-$(BRANCH)
endif
endif
LDFLAGS :=-ldflags "-w -X github.com/kubernetes-csi/csi-test/cmd/csi-sanity.VERSION=$(VERSION) -extldflags '-z relro -z now'"
PACKAGE :=$(DIR)/dist/$(APP_NAME)-$(RELEASEVER).$(GOOS).$(ARCH).tar.gz
all: $(APP_NAME)
$(APP_NAME): Makefile sanity_test.go
go test $(LDFLAGS) -c -o $(APP_NAME)
install: $(APP_NAME)
cp $(APP_NAME) $(GOPATH)/bin
clean:
rm -f csi-sanity
dist-clean:
rm -rf $(DIR)/dist
dist: clean $(PACKAGE)
$(PACKAGE): $(APP_NAME)
@echo Packaging Binaries...
@mkdir -p tmp/$(APP_NAME)
@cp $(APP_NAME) tmp/$(APP_NAME)/
@mkdir -p $(DIR)/dist/
tar -czf $@ -C tmp $(APP_NAME);
@rm -rf tmp
@echo
@echo Package $@ saved in dist directory
linux_amd64_dist:
GOOS=linux GOARCH=amd64 $(MAKE) dist
linux_arm64_dist:
GOOS=linux GOARCH=arm64 $(MAKE) dist
darwin_amd64_dist:
GOOS=darwin GOARCH=amd64 $(MAKE) dist
release: dist-clean darwin_amd64_dist linux_amd64_dist linux_arm64_dist
.PHONY: release darwin_amd64_dist linux_arm64_dist linux_amd64_dist \
linux_arm_dist linux_amd64_dist clean dist-clean

View File

@@ -1,58 +0,0 @@
# Sanity Test Command Line Program
This is the command line program that tests a CSI driver using the [`sanity`](https://github.com/kubernetes-csi/csi-test/tree/master/pkg/sanity) package test suite.
Example:
```
$ csi-sanity --csi.endpoint=<your csi driver endpoint>
```
If you want to specify a mount point:
```
$ csi-sanity --csi.endpoint=<your csi driver endpoint> --csi.mountpoint=/mnt
```
For verbose type:
```
$ csi-sanity --ginkgo.v --csi.endpoint=<your csi driver endpoint>
```
For csi-credentials, create a secrets file with all the secrets in it:
```yaml
CreateVolumeSecret:
secretKey: secretval1
DeleteVolumeSecret:
secretKey: secretval2
ControllerPublishVolumeSecret:
secretKey: secretval3
ControllerUnpublishVolumeSecret:
secretKey: secretval4
NodeStageVolumeSecret:
secretKey: secretval5
NodePublishVolumeSecret:
secretKey: secretval6
```
Pass the file path to csi-sanity as:
```
$ csi-sanity --csi.endpoint=<your csi driver endpoint> --csi.secrets=<path to secrets file>
```
Replace the keys and values of the credentials appropriately. Since the whole
secret is passed in the request, multiple key-val pairs can be used.
### Help
The full Ginkgo and golang unit test parameters are available. Type
```
$ csi-sanity -h
```
to get more information
### Download
Please see the [Releases](https://github.com/kubernetes-csi/csi-test/releases) page
to download the latest version of `csi-sanity`

View File

@@ -1,57 +0,0 @@
/*
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 (
"flag"
"fmt"
"os"
"testing"
"github.com/kubernetes-csi/csi-test/pkg/sanity"
)
const (
prefix string = "csi."
)
var (
VERSION = "(dev)"
version bool
config sanity.Config
)
func init() {
flag.StringVar(&config.Address, prefix+"endpoint", "", "CSI endpoint")
flag.BoolVar(&version, prefix+"version", false, "Version of this program")
flag.StringVar(&config.TargetPath, prefix+"mountdir", os.TempDir()+"/csi", "Mount point for NodePublish")
flag.StringVar(&config.StagingPath, prefix+"stagingdir", os.TempDir()+"/csi", "Mount point for NodeStage if staging is supported")
flag.StringVar(&config.SecretsFile, prefix+"secrets", "", "CSI secrets file")
flag.Int64Var(&config.TestVolumeSize, prefix+"testvolumesize", sanity.DefTestVolumeSize, "Base volume size used for provisioned volumes")
flag.StringVar(&config.TestVolumeParametersFile, prefix+"testvolumeparameters", "", "YAML file of volume parameters for provisioned volumes")
flag.Parse()
}
func TestSanity(t *testing.T) {
if version {
fmt.Printf("Version = %s\n", VERSION)
return
}
if len(config.Address) == 0 {
t.Fatalf("--%sendpoint must be provided with an CSI endpoint", prefix)
}
sanity.Test(t, &config)
}

View File

@@ -1,3 +0,0 @@
# Kubernetes Community Code of Conduct
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)

View File

@@ -1,18 +0,0 @@
package apitest
import (
"os"
"testing"
"github.com/kubernetes-csi/csi-test/pkg/sanity"
)
func TestMyDriver(t *testing.T) {
config := &sanity.Config{
TargetPath: os.TempDir() + "/csi",
StagingPath: os.TempDir() + "/csi",
Address: "/tmp/e2e-csi-sanity.sock",
}
sanity.Test(t, config)
}

View File

@@ -1,42 +0,0 @@
package embedded
import (
"os"
"testing"
"github.com/kubernetes-csi/csi-test/pkg/sanity"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestMyDriverGinkgo(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "CSI Sanity Test Suite")
}
// The test suite into which the sanity tests get embedded may already
// have before/after suite functions. There can only be one such
// function. Here we define empty ones because then Ginkgo
// will start complaining at runtime when invoking the embedded case
// in hack/e2e.sh if a PR adds back such functions in the sanity test
// code.
var _ = BeforeSuite(func() {})
var _ = AfterSuite(func() {})
var _ = Describe("MyCSIDriver", func() {
Context("Config A", func() {
config := &sanity.Config{
TargetPath: os.TempDir() + "/csi",
StagingPath: os.TempDir() + "/csi",
Address: "/tmp/e2e-csi-sanity.sock",
}
BeforeEach(func() {})
AfterEach(func() {})
Describe("CSI Driver Test Suite", func() {
sanity.GinkgoTest(config)
})
})
})

View File

@@ -1,72 +0,0 @@
#!/bin/bash
TESTARGS=$@
UDS="/tmp/e2e-csi-sanity.sock"
CSI_ENDPOINTS="$CSI_ENDPOINTS ${UDS}"
CSI_MOCK_VERSION="master"
#
# $1 - endpoint for mock.
# $2 - endpoint for csi-sanity in Grpc format.
# See https://github.com/grpc/grpc/blob/master/doc/naming.md
runTest()
{
CSI_ENDPOINT=$1 ./bin/mock &
local pid=$!
./cmd/csi-sanity/csi-sanity $TESTARGS --csi.endpoint=$2; ret=$?
kill -9 $pid
if [ $ret -ne 0 ] ; then
exit $ret
fi
}
runTestWithCreds()
{
CSI_ENDPOINT=$1 CSI_ENABLE_CREDS=true ./bin/mock &
local pid=$!
./cmd/csi-sanity/csi-sanity $TESTARGS --csi.endpoint=$2 --csi.secrets=mock/mocksecret.yaml; ret=$?
kill -9 $pid
if [ $ret -ne 0 ] ; then
exit $ret
fi
}
runTestAPI()
{
CSI_ENDPOINT=$1 ./bin/mock &
local pid=$!
GOCACHE=off go test -v ./hack/_apitest/api_test.go; ret=$?
if [ $ret -ne 0 ] ; then
exit $ret
fi
GOCACHE=off go test -v ./hack/_embedded/embedded_test.go; ret=$?
kill -9 $pid
if [ $ret -ne 0 ] ; then
exit $ret
fi
}
make
cd cmd/csi-sanity
make clean install || exit 1
cd ../..
runTest "${UDS}" "${UDS}"
rm -f $UDS
runTestWithCreds "${UDS}" "${UDS}"
rm -f $UDS
runTestAPI "${UDS}"
rm -f $UDS
exit 0

View File

@@ -1,22 +0,0 @@
# Mock CSI Driver
Extremely simple mock driver used to test `csi-sanity` based on `rexray/gocsi/mock`.
It can be used for testing of Container Orchestrators that implement client side
of CSI interface.
```
Usage of mock:
-disable-attach
Disables RPC_PUBLISH_UNPUBLISH_VOLUME capability.
-name string
CSI driver name. (default "io.kubernetes.storage.mock")
```
It prints all received CSI messages to stdout encoded as json, so a test can check that
CO sent the right CSI message.
Example of such output:
```
gRPCCall: {"Method":"/csi.v0.Controller/ControllerGetCapabilities","Request":{},"Response":{"capabilities":[{"Type":{"Rpc":{"type":1}}},{"Type":{"Rpc":{"type":3}}},{"Type":{"Rpc":{"type":4}}},{"Type":{"Rpc":{"type":6}}},{"Type":{"Rpc":{"type":5}}},{"Type":{"Rpc":{"type":2}}}]},"Error":""}
gRPCCall: {"Method":"/csi.v0.Controller/ControllerPublishVolume","Request":{"volume_id":"12","node_id":"some-fake-node-id","volume_capability":{"AccessType":{"Mount":{}},"access_mode":{"mode":1}}},"Response":null,"Error":"rpc error: code = NotFound desc = Not matching Node ID some-fake-node-id to Mock Node ID io.kubernetes.storage.mock"}
```

View File

@@ -1,89 +0,0 @@
package cache
import (
"strings"
"sync"
"github.com/container-storage-interface/spec/lib/go/csi"
)
type SnapshotCache interface {
Add(snapshot Snapshot)
Delete(i int)
List(ready bool) []csi.Snapshot
FindSnapshot(k, v string) (int, Snapshot)
}
type Snapshot struct {
Name string
Parameters map[string]string
SnapshotCSI csi.Snapshot
}
type snapshotCache struct {
snapshotsRWL sync.RWMutex
snapshots []Snapshot
}
func NewSnapshotCache() SnapshotCache {
return &snapshotCache{
snapshots: make([]Snapshot, 0),
}
}
func (snap *snapshotCache) Add(snapshot Snapshot) {
snap.snapshotsRWL.Lock()
defer snap.snapshotsRWL.Unlock()
snap.snapshots = append(snap.snapshots, snapshot)
}
func (snap *snapshotCache) Delete(i int) {
snap.snapshotsRWL.Lock()
defer snap.snapshotsRWL.Unlock()
copy(snap.snapshots[i:], snap.snapshots[i+1:])
snap.snapshots = snap.snapshots[:len(snap.snapshots)-1]
}
func (snap *snapshotCache) List(ready bool) []csi.Snapshot {
snap.snapshotsRWL.RLock()
defer snap.snapshotsRWL.RUnlock()
snapshots := make([]csi.Snapshot, 0)
for _, v := range snap.snapshots {
if v.SnapshotCSI.GetReadyToUse() {
snapshots = append(snapshots, v.SnapshotCSI)
}
}
return snapshots
}
func (snap *snapshotCache) FindSnapshot(k, v string) (int, Snapshot) {
snap.snapshotsRWL.RLock()
defer snap.snapshotsRWL.RUnlock()
snapshotIdx := -1
for i, vi := range snap.snapshots {
switch k {
case "id":
if strings.EqualFold(v, vi.SnapshotCSI.GetSnapshotId()) {
return i, vi
}
case "sourceVolumeId":
if strings.EqualFold(v, vi.SnapshotCSI.SourceVolumeId) {
return i, vi
}
case "name":
if vi.Name == v {
return i, vi
}
}
}
return snapshotIdx, Snapshot{}
}

View File

@@ -1,95 +0,0 @@
/*
Copyright 2018 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 main
import (
"flag"
"fmt"
"net"
"os"
"os/signal"
"strings"
"syscall"
"github.com/kubernetes-csi/csi-test/driver"
"github.com/kubernetes-csi/csi-test/mock/service"
)
func main() {
var config service.Config
flag.BoolVar(&config.DisableAttach, "disable-attach", false, "Disables RPC_PUBLISH_UNPUBLISH_VOLUME capability.")
flag.StringVar(&config.DriverName, "name", service.Name, "CSI driver name.")
flag.Int64Var(&config.AttachLimit, "attach-limit", 0, "number of attachable volumes on a node")
flag.Parse()
endpoint := os.Getenv("CSI_ENDPOINT")
if len(endpoint) == 0 {
fmt.Println("CSI_ENDPOINT must be defined and must be a path")
os.Exit(1)
}
if strings.Contains(endpoint, ":") {
fmt.Println("CSI_ENDPOINT must be a unix path")
os.Exit(1)
}
// Create mock driver
s := service.New(config)
servers := &driver.CSIDriverServers{
Controller: s,
Identity: s,
Node: s,
}
d := driver.NewCSIDriver(servers)
// If creds is enabled, set the default creds.
setCreds := os.Getenv("CSI_ENABLE_CREDS")
if len(setCreds) > 0 && setCreds == "true" {
d.SetDefaultCreds()
}
// Listen
os.Remove(endpoint)
l, err := net.Listen("unix", endpoint)
if err != nil {
fmt.Printf("Error: Unable to listen on %s socket: %v\n",
endpoint,
err)
os.Exit(1)
}
defer os.Remove(endpoint)
// Start server
if err := d.Start(l); err != nil {
fmt.Printf("Error: Unable to start mock CSI server: %v\n",
err)
os.Exit(1)
}
fmt.Println("mock driver started")
// Wait for signal
sigc := make(chan os.Signal, 1)
sigs := []os.Signal{
syscall.SIGTERM,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGQUIT,
}
signal.Notify(sigc, sigs...)
<-sigc
d.Stop()
fmt.Println("mock driver stopped")
}

View File

@@ -1,16 +0,0 @@
CreateVolumeSecret:
secretKey: secretval1
DeleteVolumeSecret:
secretKey: secretval2
ControllerPublishVolumeSecret:
secretKey: secretval3
ControllerUnpublishVolumeSecret:
secretKey: secretval4
NodeStageVolumeSecret:
secretKey: secretval5
NodePublishVolumeSecret:
secretKey: secretval6
CreateSnapshotSecret:
secretKey: secretval7
DeleteSnapshotSecret:
secretKey: secretval8

View File

@@ -1,577 +0,0 @@
package service
import (
"fmt"
"math"
"path"
"reflect"
"strconv"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/container-storage-interface/spec/lib/go/csi"
)
const (
MaxStorageCapacity = tib
ReadOnlyKey = "readonly"
)
func (s *service) CreateVolume(
ctx context.Context,
req *csi.CreateVolumeRequest) (
*csi.CreateVolumeResponse, error) {
if len(req.Name) == 0 {
return nil, status.Error(codes.InvalidArgument, "Volume Name cannot be empty")
}
if req.VolumeCapabilities == nil {
return nil, status.Error(codes.InvalidArgument, "Volume Capabilities cannot be empty")
}
// Check to see if the volume already exists.
if i, v := s.findVolByName(ctx, req.Name); i >= 0 {
// Requested volume name already exists, need to check if the existing volume's
// capacity is more or equal to new request's capacity.
if v.GetCapacityBytes() < req.GetCapacityRange().GetRequiredBytes() {
return nil, status.Error(codes.AlreadyExists,
fmt.Sprintf("Volume with name %s already exists", req.GetName()))
}
return &csi.CreateVolumeResponse{Volume: &v}, nil
}
// If no capacity is specified then use 100GiB
capacity := gib100
if cr := req.CapacityRange; cr != nil {
if rb := cr.RequiredBytes; rb > 0 {
capacity = rb
}
if lb := cr.LimitBytes; lb > 0 {
capacity = lb
}
}
// Check for maximum available capacity
if capacity >= MaxStorageCapacity {
return nil, status.Errorf(codes.OutOfRange, "Requested capacity %d exceeds maximum allowed %d", capacity, MaxStorageCapacity)
}
// Create the volume and add it to the service's in-mem volume slice.
v := s.newVolume(req.Name, capacity)
s.volsRWL.Lock()
defer s.volsRWL.Unlock()
s.vols = append(s.vols, v)
MockVolumes[v.GetVolumeId()] = Volume{
VolumeCSI: v,
NodeID: "",
ISStaged: false,
ISPublished: false,
StageTargetPath: "",
TargetPath: "",
}
return &csi.CreateVolumeResponse{Volume: &v}, nil
}
func (s *service) DeleteVolume(
ctx context.Context,
req *csi.DeleteVolumeRequest) (
*csi.DeleteVolumeResponse, error) {
s.volsRWL.Lock()
defer s.volsRWL.Unlock()
// If the volume is not specified, return error
if len(req.VolumeId) == 0 {
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
}
// If the volume does not exist then return an idempotent response.
i, _ := s.findVolNoLock("id", req.VolumeId)
if i < 0 {
return &csi.DeleteVolumeResponse{}, nil
}
// This delete logic preserves order and prevents potential memory
// leaks. The slice's elements may not be pointers, but the structs
// themselves have fields that are.
copy(s.vols[i:], s.vols[i+1:])
s.vols[len(s.vols)-1] = csi.Volume{}
s.vols = s.vols[:len(s.vols)-1]
log.WithField("volumeID", req.VolumeId).Debug("mock delete volume")
return &csi.DeleteVolumeResponse{}, nil
}
func (s *service) ControllerPublishVolume(
ctx context.Context,
req *csi.ControllerPublishVolumeRequest) (
*csi.ControllerPublishVolumeResponse, error) {
if s.config.DisableAttach {
return nil, status.Error(codes.Unimplemented, "ControllerPublish is not supported")
}
if len(req.VolumeId) == 0 {
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
}
if len(req.NodeId) == 0 {
return nil, status.Error(codes.InvalidArgument, "Node ID cannot be empty")
}
if req.VolumeCapability == nil {
return nil, status.Error(codes.InvalidArgument, "Volume Capabilities cannot be empty")
}
if req.NodeId != s.nodeID {
return nil, status.Errorf(codes.NotFound, "Not matching Node ID %s to Mock Node ID %s", req.NodeId, s.nodeID)
}
s.volsRWL.Lock()
defer s.volsRWL.Unlock()
i, v := s.findVolNoLock("id", req.VolumeId)
if i < 0 {
return nil, status.Error(codes.NotFound, req.VolumeId)
}
// devPathKey is the key in the volume's attributes that is set to a
// mock device path if the volume has been published by the controller
// to the specified node.
devPathKey := path.Join(req.NodeId, "dev")
// Check to see if the volume is already published.
if device := v.VolumeContext[devPathKey]; device != "" {
var volRo bool
var roVal string
if ro, ok := v.VolumeContext[ReadOnlyKey]; ok {
roVal = ro
}
if roVal == "true" {
volRo = true
} else {
volRo = false
}
// Check if readonly flag is compatible with the publish request.
if req.GetReadonly() != volRo {
return nil, status.Error(codes.AlreadyExists, "Volume published but has incompatible readonly flag")
}
return &csi.ControllerPublishVolumeResponse{
PublishContext: map[string]string{
"device": device,
"readonly": roVal,
},
}, nil
}
var roVal string
if req.GetReadonly() {
roVal = "true"
} else {
roVal = "false"
}
// Publish the volume.
device := "/dev/mock"
v.VolumeContext[devPathKey] = device
v.VolumeContext[ReadOnlyKey] = roVal
s.vols[i] = v
return &csi.ControllerPublishVolumeResponse{
PublishContext: map[string]string{
"device": device,
"readonly": roVal,
},
}, nil
}
func (s *service) ControllerUnpublishVolume(
ctx context.Context,
req *csi.ControllerUnpublishVolumeRequest) (
*csi.ControllerUnpublishVolumeResponse, error) {
if s.config.DisableAttach {
return nil, status.Error(codes.Unimplemented, "ControllerPublish is not supported")
}
if len(req.VolumeId) == 0 {
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
}
nodeID := req.NodeId
if len(nodeID) == 0 {
// If node id is empty, no failure as per Spec
nodeID = s.nodeID
}
if req.NodeId != s.nodeID {
return nil, status.Errorf(codes.NotFound, "Node ID %s does not match to expected Node ID %s", req.NodeId, s.nodeID)
}
s.volsRWL.Lock()
defer s.volsRWL.Unlock()
i, v := s.findVolNoLock("id", req.VolumeId)
if i < 0 {
return nil, status.Error(codes.NotFound, req.VolumeId)
}
// devPathKey is the key in the volume's attributes that is set to a
// mock device path if the volume has been published by the controller
// to the specified node.
devPathKey := path.Join(nodeID, "dev")
// Check to see if the volume is already unpublished.
if v.VolumeContext[devPathKey] == "" {
return &csi.ControllerUnpublishVolumeResponse{}, nil
}
// Unpublish the volume.
delete(v.VolumeContext, devPathKey)
delete(v.VolumeContext, ReadOnlyKey)
s.vols[i] = v
return &csi.ControllerUnpublishVolumeResponse{}, nil
}
func (s *service) ValidateVolumeCapabilities(
ctx context.Context,
req *csi.ValidateVolumeCapabilitiesRequest) (
*csi.ValidateVolumeCapabilitiesResponse, error) {
if len(req.GetVolumeId()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
}
if len(req.VolumeCapabilities) == 0 {
return nil, status.Error(codes.InvalidArgument, req.VolumeId)
}
i, _ := s.findVolNoLock("id", req.VolumeId)
if i < 0 {
return nil, status.Error(codes.NotFound, req.VolumeId)
}
return &csi.ValidateVolumeCapabilitiesResponse{
Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{
VolumeContext: req.GetVolumeContext(),
VolumeCapabilities: req.GetVolumeCapabilities(),
Parameters: req.GetParameters(),
},
}, nil
}
func (s *service) ListVolumes(
ctx context.Context,
req *csi.ListVolumesRequest) (
*csi.ListVolumesResponse, error) {
// Copy the mock volumes into a new slice in order to avoid
// locking the service's volume slice for the duration of the
// ListVolumes RPC.
var vols []csi.Volume
func() {
s.volsRWL.RLock()
defer s.volsRWL.RUnlock()
vols = make([]csi.Volume, len(s.vols))
copy(vols, s.vols)
}()
var (
ulenVols = int32(len(vols))
maxEntries = req.MaxEntries
startingToken int32
)
if v := req.StartingToken; v != "" {
i, err := strconv.ParseUint(v, 10, 32)
if err != nil {
return nil, status.Errorf(
codes.InvalidArgument,
"startingToken=%d !< int32=%d",
startingToken, math.MaxUint32)
}
startingToken = int32(i)
}
if startingToken > ulenVols {
return nil, status.Errorf(
codes.InvalidArgument,
"startingToken=%d > len(vols)=%d",
startingToken, ulenVols)
}
// Discern the number of remaining entries.
rem := ulenVols - startingToken
// If maxEntries is 0 or greater than the number of remaining entries then
// set maxEntries to the number of remaining entries.
if maxEntries == 0 || maxEntries > rem {
maxEntries = rem
}
var (
i int
j = startingToken
entries = make(
[]*csi.ListVolumesResponse_Entry,
maxEntries)
)
for i = 0; i < len(entries); i++ {
entries[i] = &csi.ListVolumesResponse_Entry{
Volume: &vols[j],
}
j++
}
var nextToken string
if n := startingToken + int32(i); n < ulenVols {
nextToken = fmt.Sprintf("%d", n)
}
return &csi.ListVolumesResponse{
Entries: entries,
NextToken: nextToken,
}, nil
}
func (s *service) GetCapacity(
ctx context.Context,
req *csi.GetCapacityRequest) (
*csi.GetCapacityResponse, error) {
return &csi.GetCapacityResponse{
AvailableCapacity: MaxStorageCapacity,
}, nil
}
func (s *service) ControllerGetCapabilities(
ctx context.Context,
req *csi.ControllerGetCapabilitiesRequest) (
*csi.ControllerGetCapabilitiesResponse, error) {
caps := []*csi.ControllerServiceCapability{
{
Type: &csi.ControllerServiceCapability_Rpc{
Rpc: &csi.ControllerServiceCapability_RPC{
Type: csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
},
},
},
{
Type: &csi.ControllerServiceCapability_Rpc{
Rpc: &csi.ControllerServiceCapability_RPC{
Type: csi.ControllerServiceCapability_RPC_LIST_VOLUMES,
},
},
},
{
Type: &csi.ControllerServiceCapability_Rpc{
Rpc: &csi.ControllerServiceCapability_RPC{
Type: csi.ControllerServiceCapability_RPC_GET_CAPACITY,
},
},
},
{
Type: &csi.ControllerServiceCapability_Rpc{
Rpc: &csi.ControllerServiceCapability_RPC{
Type: csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
},
},
},
{
Type: &csi.ControllerServiceCapability_Rpc{
Rpc: &csi.ControllerServiceCapability_RPC{
Type: csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
},
},
},
}
if !s.config.DisableAttach {
caps = append(caps, &csi.ControllerServiceCapability{
Type: &csi.ControllerServiceCapability_Rpc{
Rpc: &csi.ControllerServiceCapability_RPC{
Type: csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
},
},
})
}
return &csi.ControllerGetCapabilitiesResponse{
Capabilities: caps,
}, nil
}
func (s *service) CreateSnapshot(ctx context.Context,
req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) {
// Check arguments
if len(req.GetName()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Snapshot Name cannot be empty")
}
if len(req.GetSourceVolumeId()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Snapshot SourceVolumeId cannot be empty")
}
// Check to see if the snapshot already exists.
if i, v := s.snapshots.FindSnapshot("name", req.GetName()); i >= 0 {
// Requested snapshot name already exists
if v.SnapshotCSI.GetSourceVolumeId() != req.GetSourceVolumeId() || !reflect.DeepEqual(v.Parameters, req.GetParameters()) {
return nil, status.Error(codes.AlreadyExists,
fmt.Sprintf("Snapshot with name %s already exists", req.GetName()))
}
return &csi.CreateSnapshotResponse{Snapshot: &v.SnapshotCSI}, nil
}
// Create the snapshot and add it to the service's in-mem snapshot slice.
snapshot := s.newSnapshot(req.GetName(), req.GetSourceVolumeId(), req.GetParameters())
s.snapshots.Add(snapshot)
return &csi.CreateSnapshotResponse{Snapshot: &snapshot.SnapshotCSI}, nil
}
func (s *service) DeleteSnapshot(ctx context.Context,
req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) {
// If the snapshot is not specified, return error
if len(req.SnapshotId) == 0 {
return nil, status.Error(codes.InvalidArgument, "Snapshot ID cannot be empty")
}
// If the snapshot does not exist then return an idempotent response.
i, _ := s.snapshots.FindSnapshot("id", req.SnapshotId)
if i < 0 {
return &csi.DeleteSnapshotResponse{}, nil
}
// This delete logic preserves order and prevents potential memory
// leaks. The slice's elements may not be pointers, but the structs
// themselves have fields that are.
s.snapshots.Delete(i)
log.WithField("SnapshotId", req.SnapshotId).Debug("mock delete snapshot")
return &csi.DeleteSnapshotResponse{}, nil
}
func (s *service) ListSnapshots(ctx context.Context,
req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
// case 1: SnapshotId is not empty, return snapshots that match the snapshot id.
if len(req.GetSnapshotId()) != 0 {
return getSnapshotById(s, req)
}
// case 2: SourceVolumeId is not empty, return snapshots that match the source volume id.
if len(req.GetSourceVolumeId()) != 0 {
return getSnapshotByVolumeId(s, req)
}
// case 3: no parameter is set, so we return all the snapshots.
return getAllSnapshots(s, req)
}
func getSnapshotById(s *service, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
if len(req.GetSnapshotId()) != 0 {
i, snapshot := s.snapshots.FindSnapshot("id", req.GetSnapshotId())
if i < 0 {
return &csi.ListSnapshotsResponse{}, nil
}
if len(req.GetSourceVolumeId()) != 0 {
if snapshot.SnapshotCSI.GetSourceVolumeId() != req.GetSourceVolumeId() {
return &csi.ListSnapshotsResponse{}, nil
}
}
return &csi.ListSnapshotsResponse{
Entries: []*csi.ListSnapshotsResponse_Entry{
{
Snapshot: &snapshot.SnapshotCSI,
},
},
}, nil
}
return nil, nil
}
func getSnapshotByVolumeId(s *service, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
if len(req.GetSourceVolumeId()) != 0 {
i, snapshot := s.snapshots.FindSnapshot("sourceVolumeId", req.SourceVolumeId)
if i < 0 {
return &csi.ListSnapshotsResponse{}, nil
}
return &csi.ListSnapshotsResponse{
Entries: []*csi.ListSnapshotsResponse_Entry{
{
Snapshot: &snapshot.SnapshotCSI,
},
},
}, nil
}
return nil, nil
}
func getAllSnapshots(s *service, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
// Copy the mock snapshots into a new slice in order to avoid
// locking the service's snapshot slice for the duration of the
// ListSnapshots RPC.
readyToUse := true
snapshots := s.snapshots.List(readyToUse)
var (
ulenSnapshots = int32(len(snapshots))
maxEntries = req.MaxEntries
startingToken int32
)
if v := req.StartingToken; v != "" {
i, err := strconv.ParseUint(v, 10, 32)
if err != nil {
return nil, status.Errorf(
codes.Aborted,
"startingToken=%d !< int32=%d",
startingToken, math.MaxUint32)
}
startingToken = int32(i)
}
if startingToken > ulenSnapshots {
return nil, status.Errorf(
codes.Aborted,
"startingToken=%d > len(snapshots)=%d",
startingToken, ulenSnapshots)
}
// Discern the number of remaining entries.
rem := ulenSnapshots - startingToken
// If maxEntries is 0 or greater than the number of remaining entries then
// set maxEntries to the number of remaining entries.
if maxEntries == 0 || maxEntries > rem {
maxEntries = rem
}
var (
i int
j = startingToken
entries = make(
[]*csi.ListSnapshotsResponse_Entry,
maxEntries)
)
for i = 0; i < len(entries); i++ {
entries[i] = &csi.ListSnapshotsResponse_Entry{
Snapshot: &snapshots[j],
}
j++
}
var nextToken string
if n := startingToken + int32(i); n < ulenSnapshots {
nextToken = fmt.Sprintf("%d", n)
}
return &csi.ListSnapshotsResponse{
Entries: entries,
NextToken: nextToken,
}, nil
}

View File

@@ -1,48 +0,0 @@
package service
import (
"golang.org/x/net/context"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/protobuf/ptypes/wrappers"
)
func (s *service) GetPluginInfo(
ctx context.Context,
req *csi.GetPluginInfoRequest) (
*csi.GetPluginInfoResponse, error) {
return &csi.GetPluginInfoResponse{
Name: s.config.DriverName,
VendorVersion: VendorVersion,
Manifest: Manifest,
}, nil
}
func (s *service) Probe(
ctx context.Context,
req *csi.ProbeRequest) (
*csi.ProbeResponse, error) {
return &csi.ProbeResponse{
Ready: &wrappers.BoolValue{Value: true},
}, nil
}
func (s *service) GetPluginCapabilities(
ctx context.Context,
req *csi.GetPluginCapabilitiesRequest) (
*csi.GetPluginCapabilitiesResponse, error) {
return &csi.GetPluginCapabilitiesResponse{
Capabilities: []*csi.PluginCapability{
{
Type: &csi.PluginCapability_Service_{
Service: &csi.PluginCapability_Service{
Type: csi.PluginCapability_Service_CONTROLLER_SERVICE,
},
},
},
},
}, nil
}

View File

@@ -1,244 +0,0 @@
package service
import (
"path"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"golang.org/x/net/context"
"github.com/container-storage-interface/spec/lib/go/csi"
)
func (s *service) NodeStageVolume(
ctx context.Context,
req *csi.NodeStageVolumeRequest) (
*csi.NodeStageVolumeResponse, error) {
device, ok := req.PublishContext["device"]
if !ok {
if s.config.DisableAttach {
device = "mock device"
} else {
return nil, status.Error(
codes.InvalidArgument,
"stage volume info 'device' key required")
}
}
if len(req.GetVolumeId()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
}
if len(req.GetStagingTargetPath()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Staging Target Path cannot be empty")
}
if req.GetVolumeCapability() == nil {
return nil, status.Error(codes.InvalidArgument, "Volume Capability cannot be empty")
}
s.volsRWL.Lock()
defer s.volsRWL.Unlock()
i, v := s.findVolNoLock("id", req.VolumeId)
if i < 0 {
return nil, status.Error(codes.NotFound, req.VolumeId)
}
// nodeStgPathKey is the key in the volume's attributes that is set to a
// mock stage path if the volume has been published by the node
nodeStgPathKey := path.Join(s.nodeID, req.StagingTargetPath)
// Check to see if the volume has already been staged.
if v.VolumeContext[nodeStgPathKey] != "" {
// TODO: Check for the capabilities to be equal. Return "ALREADY_EXISTS"
// if the capabilities don't match.
return &csi.NodeStageVolumeResponse{}, nil
}
// Stage the volume.
v.VolumeContext[nodeStgPathKey] = device
s.vols[i] = v
return &csi.NodeStageVolumeResponse{}, nil
}
func (s *service) NodeUnstageVolume(
ctx context.Context,
req *csi.NodeUnstageVolumeRequest) (
*csi.NodeUnstageVolumeResponse, error) {
if len(req.GetVolumeId()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
}
if len(req.GetStagingTargetPath()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Staging Target Path cannot be empty")
}
s.volsRWL.Lock()
defer s.volsRWL.Unlock()
i, v := s.findVolNoLock("id", req.VolumeId)
if i < 0 {
return nil, status.Error(codes.NotFound, req.VolumeId)
}
// nodeStgPathKey is the key in the volume's attributes that is set to a
// mock stage path if the volume has been published by the node
nodeStgPathKey := path.Join(s.nodeID, req.StagingTargetPath)
// Check to see if the volume has already been unstaged.
if v.VolumeContext[nodeStgPathKey] == "" {
return &csi.NodeUnstageVolumeResponse{}, nil
}
// Unpublish the volume.
delete(v.VolumeContext, nodeStgPathKey)
s.vols[i] = v
return &csi.NodeUnstageVolumeResponse{}, nil
}
func (s *service) NodePublishVolume(
ctx context.Context,
req *csi.NodePublishVolumeRequest) (
*csi.NodePublishVolumeResponse, error) {
device, ok := req.PublishContext["device"]
if !ok {
if s.config.DisableAttach {
device = "mock device"
} else {
return nil, status.Error(
codes.InvalidArgument,
"stage volume info 'device' key required")
}
}
if len(req.GetVolumeId()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
}
if len(req.GetTargetPath()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Target Path cannot be empty")
}
if req.GetVolumeCapability() == nil {
return nil, status.Error(codes.InvalidArgument, "Volume Capability cannot be empty")
}
s.volsRWL.Lock()
defer s.volsRWL.Unlock()
i, v := s.findVolNoLock("id", req.VolumeId)
if i < 0 {
return nil, status.Error(codes.NotFound, req.VolumeId)
}
// nodeMntPathKey is the key in the volume's attributes that is set to a
// mock mount path if the volume has been published by the node
nodeMntPathKey := path.Join(s.nodeID, req.TargetPath)
// Check to see if the volume has already been published.
if v.VolumeContext[nodeMntPathKey] != "" {
// Requests marked Readonly fail due to volumes published by
// the Mock driver supporting only RW mode.
if req.Readonly {
return nil, status.Error(codes.AlreadyExists, req.VolumeId)
}
return &csi.NodePublishVolumeResponse{}, nil
}
// Publish the volume.
if req.GetStagingTargetPath() != "" {
v.VolumeContext[nodeMntPathKey] = req.GetStagingTargetPath()
} else {
v.VolumeContext[nodeMntPathKey] = device
}
s.vols[i] = v
return &csi.NodePublishVolumeResponse{}, nil
}
func (s *service) NodeUnpublishVolume(
ctx context.Context,
req *csi.NodeUnpublishVolumeRequest) (
*csi.NodeUnpublishVolumeResponse, error) {
if len(req.GetVolumeId()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
}
if len(req.GetTargetPath()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Target Path cannot be empty")
}
s.volsRWL.Lock()
defer s.volsRWL.Unlock()
i, v := s.findVolNoLock("id", req.VolumeId)
if i < 0 {
return nil, status.Error(codes.NotFound, req.VolumeId)
}
// nodeMntPathKey is the key in the volume's attributes that is set to a
// mock mount path if the volume has been published by the node
nodeMntPathKey := path.Join(s.nodeID, req.TargetPath)
// Check to see if the volume has already been unpublished.
if v.VolumeContext[nodeMntPathKey] == "" {
return &csi.NodeUnpublishVolumeResponse{}, nil
}
// Unpublish the volume.
delete(v.VolumeContext, nodeMntPathKey)
s.vols[i] = v
return &csi.NodeUnpublishVolumeResponse{}, nil
}
func (s *service) NodeGetCapabilities(
ctx context.Context,
req *csi.NodeGetCapabilitiesRequest) (
*csi.NodeGetCapabilitiesResponse, error) {
return &csi.NodeGetCapabilitiesResponse{
Capabilities: []*csi.NodeServiceCapability{
{
Type: &csi.NodeServiceCapability_Rpc{
Rpc: &csi.NodeServiceCapability_RPC{
Type: csi.NodeServiceCapability_RPC_UNKNOWN,
},
},
},
{
Type: &csi.NodeServiceCapability_Rpc{
Rpc: &csi.NodeServiceCapability_RPC{
Type: csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
},
},
},
},
}, nil
}
func (s *service) NodeGetInfo(ctx context.Context,
req *csi.NodeGetInfoRequest) (*csi.NodeGetInfoResponse, error) {
csiNodeResponse := &csi.NodeGetInfoResponse{
NodeId: s.nodeID,
}
if s.config.AttachLimit > 0 {
csiNodeResponse.MaxVolumesPerNode = s.config.AttachLimit
}
return csiNodeResponse, nil
}
func (s *service) NodeGetVolumeStats(ctx context.Context,
req *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) {
return &csi.NodeGetVolumeStatsResponse{}, nil
}

View File

@@ -1,147 +0,0 @@
package service
import (
"fmt"
"strings"
"sync"
"sync/atomic"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/kubernetes-csi/csi-test/mock/cache"
"golang.org/x/net/context"
"github.com/golang/protobuf/ptypes"
)
const (
// Name is the name of the CSI plug-in.
Name = "io.kubernetes.storage.mock"
// VendorVersion is the version returned by GetPluginInfo.
VendorVersion = "0.3.0"
)
// Manifest is the SP's manifest.
var Manifest = map[string]string{
"url": "https://github.com/kubernetes-csi/csi-test/mock",
}
type Config struct {
DisableAttach bool
DriverName string
AttachLimit int64
}
// Service is the CSI Mock service provider.
type Service interface {
csi.ControllerServer
csi.IdentityServer
csi.NodeServer
}
type service struct {
sync.Mutex
nodeID string
vols []csi.Volume
volsRWL sync.RWMutex
volsNID uint64
snapshots cache.SnapshotCache
snapshotsNID uint64
config Config
}
type Volume struct {
sync.Mutex
VolumeCSI csi.Volume
NodeID string
ISStaged bool
ISPublished bool
StageTargetPath string
TargetPath string
}
var MockVolumes map[string]Volume
// New returns a new Service.
func New(config Config) Service {
s := &service{
nodeID: config.DriverName,
config: config,
}
s.snapshots = cache.NewSnapshotCache()
s.vols = []csi.Volume{
s.newVolume("Mock Volume 1", gib100),
s.newVolume("Mock Volume 2", gib100),
s.newVolume("Mock Volume 3", gib100),
}
MockVolumes = map[string]Volume{}
s.snapshots.Add(s.newSnapshot("Mock Snapshot 1", "1", map[string]string{"Description": "snapshot 1"}))
s.snapshots.Add(s.newSnapshot("Mock Snapshot 2", "2", map[string]string{"Description": "snapshot 2"}))
s.snapshots.Add(s.newSnapshot("Mock Snapshot 3", "3", map[string]string{"Description": "snapshot 3"}))
return s
}
const (
kib int64 = 1024
mib int64 = kib * 1024
gib int64 = mib * 1024
gib100 int64 = gib * 100
tib int64 = gib * 1024
tib100 int64 = tib * 100
)
func (s *service) newVolume(name string, capcity int64) csi.Volume {
return csi.Volume{
VolumeId: fmt.Sprintf("%d", atomic.AddUint64(&s.volsNID, 1)),
VolumeContext: map[string]string{"name": name},
CapacityBytes: capcity,
}
}
func (s *service) findVol(k, v string) (volIdx int, volInfo csi.Volume) {
s.volsRWL.RLock()
defer s.volsRWL.RUnlock()
return s.findVolNoLock(k, v)
}
func (s *service) findVolNoLock(k, v string) (volIdx int, volInfo csi.Volume) {
volIdx = -1
for i, vi := range s.vols {
switch k {
case "id":
if strings.EqualFold(v, vi.GetVolumeId()) {
return i, vi
}
case "name":
if n, ok := vi.VolumeContext["name"]; ok && strings.EqualFold(v, n) {
return i, vi
}
}
}
return
}
func (s *service) findVolByName(
ctx context.Context, name string) (int, csi.Volume) {
return s.findVol("name", name)
}
func (s *service) newSnapshot(name, sourceVolumeId string, parameters map[string]string) cache.Snapshot {
ptime := ptypes.TimestampNow()
return cache.Snapshot{
Name: name,
Parameters: parameters,
SnapshotCSI: csi.Snapshot{
SnapshotId: fmt.Sprintf("%d", atomic.AddUint64(&s.snapshotsNID, 1)),
CreationTime: ptime,
SourceVolumeId: sourceVolumeId,
ReadyToUse: true,
},
}
}

View File

@@ -1,62 +0,0 @@
# 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(func() {
//... setup driver and config...
})
AfterEach(func() {
//...tear down driver...
})
Describe("CSI sanity", func() {
sanity.GinkgoTest(config)
})
})
Context("Config B", func () {
// other configs
})
})
```
## Command line program
Please see [csi-sanity](https://github.com/kubernetes-csi/csi-test/tree/master/cmd/csi-sanity)

View File

@@ -1,134 +0,0 @@
/*
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"
. "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().GetVolumeId() != "" {
cl.RegisterVolume(name, VolumeInfo{VolumeID: vol.GetVolume().GetVolumeId()})
}
}
// 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,
Secrets: cl.Context.Secrets.ControllerUnpublishVolumeSecret,
},
); err != nil {
logger.Printf("warning: ControllerUnpublishVolume: %s", err)
}
}
if _, err := cl.ControllerClient.DeleteVolume(
ctx,
&csi.DeleteVolumeRequest{
VolumeId: info.VolumeID,
Secrets: 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

@@ -1,99 +0,0 @@
/*
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"
. "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_VOLUME_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

@@ -1,517 +0,0 @@
/*
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"
. "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:
case csi.NodeServiceCapability_RPC_GET_VOLUME_STATS:
default:
Fail(fmt.Sprintf("Unknown capability: %v\n", cap.GetRpc().GetType()))
}
}
})
})
Describe("NodeGetInfo", func() {
var (
i csi.IdentityClient
accessibilityConstraintSupported bool
)
BeforeEach(func() {
i = csi.NewIdentityClient(sc.Conn)
accessibilityConstraintSupported = isPluginCapabilitySupported(i, csi.PluginCapability_Service_VOLUME_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{
Secrets: 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",
Secrets: 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,
Secrets: 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,
},
},
PublishContext: map[string]string{
"device": device,
},
Secrets: 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,
},
},
PublishContext: map[string]string{
"device": device,
},
Secrets: 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,
PublishContext: map[string]string{
"device": device,
},
Secrets: 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,
},
},
},
Secrets: sc.Secrets.CreateVolumeSecret,
},
)
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()})
By("getting a node id")
nid, err := c.NodeGetInfo(
context.Background(),
&csi.NodeGetInfoRequest{})
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().GetVolumeId(),
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,
},
},
VolumeContext: vol.GetVolume().GetVolumeContext(),
Readonly: false,
Secrets: sc.Secrets.ControllerPublishVolumeSecret,
},
)
Expect(err).NotTo(HaveOccurred())
cl.RegisterVolume(name, VolumeInfo{VolumeID: vol.GetVolume().GetVolumeId(), 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().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.Config.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.Config.StagingPath
}
nodepubvol, err := c.NodePublishVolume(
context.Background(),
&csi.NodePublishVolumeRequest{
VolumeId: vol.GetVolume().GetVolumeId(),
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,
},
},
VolumeContext: vol.GetVolume().GetVolumeContext(),
PublishContext: conpubvol.GetPublishContext(),
Secrets: 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().GetVolumeId(),
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().GetVolumeId(),
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().GetVolumeId(),
NodeId: nid.GetNodeId(),
Secrets: 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().GetVolumeId(),
Secrets: sc.Secrets.DeleteVolumeSecret,
},
)
Expect(err).NotTo(HaveOccurred())
})
})

View File

@@ -1,194 +0,0 @@
/*
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
TestVolumeParametersFile string
TestVolumeParameters map[string]string
}
// 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
connAddress string
}
// 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) {
path := reqConfig.TestVolumeParametersFile
if len(path) != 0 {
yamlFile, err := ioutil.ReadFile(path)
if err != nil {
panic(fmt.Sprintf("failed to read file %q: %v", path, err))
}
err = yaml.Unmarshal(yamlFile, &reqConfig.TestVolumeParameters)
if err != nil {
panic(fmt.Sprintf("error unmarshaling yaml: %v", err))
}
}
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{}
}
// It is possible that a test sets sc.Config.Address
// dynamically (and differently!) in a BeforeEach, so only
// reuse the connection if the address is still the same.
if sc.Conn == nil || sc.connAddress != sc.Config.Address {
By("connecting to CSI driver")
sc.Conn, err = utils.Connect(sc.Config.Address)
Expect(err).NotTo(HaveOccurred())
sc.connAddress = sc.Config.Address
} else {
By(fmt.Sprintf("reusing connection to CSI driver at %s", sc.connAddress))
}
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() {
// We intentionally do not close the connection to the CSI
// driver here because the large amount of connection attempts
// caused test failures
// (https://github.com/kubernetes-csi/csi-test/issues/101). We
// could fix this with retries
// (https://github.com/kubernetes-csi/csi-test/pull/97) but
// that requires more discussion, so instead we just connect
// once per process instead of once per test case. This was
// also said to be faster
// (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
}
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

@@ -1,56 +0,0 @@
/*
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()
})
})
}
}

View File

@@ -1,188 +0,0 @@
/*
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 test
import (
"context"
"fmt"
"reflect"
"testing"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/mock/gomock"
"github.com/golang/protobuf/proto"
mock_driver "github.com/kubernetes-csi/csi-test/driver"
mock_utils "github.com/kubernetes-csi/csi-test/utils"
)
func TestPluginInfoResponse(t *testing.T) {
// Setup mock
m := gomock.NewController(t)
defer m.Finish()
driver := mock_driver.NewMockIdentityServer(m)
// Setup input
in := &csi.GetPluginInfoRequest{}
// Setup mock outout
out := &csi.GetPluginInfoResponse{
Name: "mock",
VendorVersion: "0.1.1",
Manifest: map[string]string{
"hello": "world",
},
}
// Setup expectation
driver.EXPECT().GetPluginInfo(nil, in).Return(out, nil).Times(1)
// Actual call
r, err := driver.GetPluginInfo(nil, in)
name := r.GetName()
if err != nil {
t.Errorf("Error: %s", err.Error())
}
if name != "mock" {
t.Errorf("Unknown name: %s\n", name)
}
}
type pbMatcher struct {
x proto.Message
}
func (p pbMatcher) Matches(x interface{}) bool {
y := x.(proto.Message)
return proto.Equal(p.x, y)
}
func (p pbMatcher) String() string {
return fmt.Sprintf("pb equal to %v", p.x)
}
func pbMatch(x interface{}) gomock.Matcher {
v := x.(proto.Message)
return &pbMatcher{v}
}
func TestGRPCGetPluginInfoReponse(t *testing.T) {
// Setup mock
m := gomock.NewController(&mock_utils.SafeGoroutineTester{})
defer m.Finish()
driver := mock_driver.NewMockIdentityServer(m)
// Setup input
in := &csi.GetPluginInfoRequest{}
// Setup mock outout
out := &csi.GetPluginInfoResponse{
Name: "mock",
VendorVersion: "0.1.1",
Manifest: map[string]string{
"hello": "world",
},
}
// Setup expectation
// !IMPORTANT!: Must set context expected value to gomock.Any() to match any value
driver.EXPECT().GetPluginInfo(gomock.Any(), pbMatch(in)).Return(out, nil).Times(1)
// Create a new RPC
server := mock_driver.NewMockCSIDriver(&mock_driver.MockCSIDriverServers{
Identity: driver,
})
conn, err := server.Nexus()
if err != nil {
t.Errorf("Error: %s", err.Error())
}
defer server.Close()
// Make call
c := csi.NewIdentityClient(conn)
r, err := c.GetPluginInfo(context.Background(), in)
if err != nil {
t.Errorf("Error: %s", err.Error())
}
name := r.GetName()
if name != "mock" {
t.Errorf("Unknown name: %s\n", name)
}
}
func TestGRPCAttach(t *testing.T) {
// Setup mock
m := gomock.NewController(&mock_utils.SafeGoroutineTester{})
defer m.Finish()
driver := mock_driver.NewMockControllerServer(m)
// Setup input
defaultVolumeID := "myname"
defaultNodeID := "MyNodeID"
defaultCaps := &csi.VolumeCapability{
AccessType: &csi.VolumeCapability_Mount{
Mount: &csi.VolumeCapability_MountVolume{},
},
AccessMode: &csi.VolumeCapability_AccessMode{
Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
},
}
publishVolumeInfo := map[string]string{
"first": "foo",
"second": "bar",
"third": "baz",
}
defaultRequest := &csi.ControllerPublishVolumeRequest{
VolumeId: defaultVolumeID,
NodeId: defaultNodeID,
VolumeCapability: defaultCaps,
Readonly: false,
}
// Setup mock outout
out := &csi.ControllerPublishVolumeResponse{
PublishContext: publishVolumeInfo,
}
// Setup expectation
// !IMPORTANT!: Must set context expected value to gomock.Any() to match any value
driver.EXPECT().ControllerPublishVolume(gomock.Any(), pbMatch(defaultRequest)).Return(out, nil).Times(1)
// Create a new RPC
server := mock_driver.NewMockCSIDriver(&mock_driver.MockCSIDriverServers{
Controller: driver,
})
conn, err := server.Nexus()
if err != nil {
t.Errorf("Error: %s", err.Error())
}
defer server.Close()
// Make call
c := csi.NewControllerClient(conn)
r, err := c.ControllerPublishVolume(context.Background(), defaultRequest)
if err != nil {
t.Errorf("Error: %s", err.Error())
}
info := r.GetPublishContext()
if !reflect.DeepEqual(info, publishVolumeInfo) {
t.Errorf("Invalid publish info: %v", info)
}
}

View File

@@ -1,127 +0,0 @@
/*
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 test
import (
"context"
"net"
"sync"
"testing"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/kubernetes-csi/csi-test/utils"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
// Example simple driver
// This example assumes that your driver will create the server and listen on
// some unix domain socket or port for tests.
type simpleDriver struct {
listener net.Listener
server *grpc.Server
wg sync.WaitGroup
}
func (s *simpleDriver) GetPluginCapabilities(context.Context, *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) {
// TODO: Return some simple Plugin Capabilities
return &csi.GetPluginCapabilitiesResponse{}, nil
}
func (s *simpleDriver) Probe(context.Context, *csi.ProbeRequest) (*csi.ProbeResponse, error) {
return &csi.ProbeResponse{}, nil
}
func (s *simpleDriver) GetPluginInfo(
context.Context, *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) {
return &csi.GetPluginInfoResponse{
Name: "simpleDriver",
VendorVersion: "0.1.1",
Manifest: map[string]string{
"hello": "world",
},
}, nil
}
func (s *simpleDriver) goServe() {
s.wg.Add(1)
go func() {
defer s.wg.Done()
s.server.Serve(s.listener)
}()
}
func (s *simpleDriver) Address() string {
return s.listener.Addr().String()
}
func (s *simpleDriver) Start() error {
// Listen on a port assigned by the net package
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return err
}
s.listener = l
// Create a new grpc server
s.server = grpc.NewServer()
csi.RegisterIdentityServer(s.server, s)
reflection.Register(s.server)
// Start listening for requests
s.goServe()
return nil
}
func (s *simpleDriver) Stop() {
s.server.Stop()
s.wg.Wait()
}
//
// Tests
//
func TestSimpleDriver(t *testing.T) {
// Setup simple driver
s := &simpleDriver{}
err := s.Start()
if err != nil {
t.Errorf("Error: %s", err.Error())
}
defer s.Stop()
// Setup a connection to the driver
conn, err := utils.Connect(s.Address())
if err != nil {
t.Errorf("Error: %s", err.Error())
}
defer conn.Close()
// Make a call
c := csi.NewIdentityClient(conn)
r, err := c.GetPluginInfo(context.Background(), &csi.GetPluginInfoRequest{})
if err != nil {
t.Errorf("Error: %s", err.Error())
}
// Verify
name := r.GetName()
if name != "simpleDriver" {
t.Errorf("Unknown name: %s\n", name)
}
}