add csi-test to vendor

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

View File

@@ -0,0 +1,2 @@
TheCodeTeam
Kubernetes Authors

View File

@@ -0,0 +1,2 @@
# Mock CSI Driver
Extremely simple mock driver used to test `csi-sanity` based on `rexray/gocsi/mock`

View File

@@ -0,0 +1,89 @@
package cache
import (
"strings"
"sync"
"github.com/container-storage-interface/spec/lib/go/csi/v0"
)
type SnapshotCache interface {
Add(snapshot Snapshot)
Delete(i int)
List(status csi.SnapshotStatus_Type) []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(status csi.SnapshotStatus_Type) []csi.Snapshot {
snap.snapshotsRWL.RLock()
defer snap.snapshotsRWL.RUnlock()
snapshots := make([]csi.Snapshot, 0)
for _, v := range snap.snapshots {
if v.SnapshotCSI.GetStatus() != nil && v.SnapshotCSI.GetStatus().Type == status {
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.Id) {
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{}
}

88
vendor/github.com/kubernetes-csi/csi-test/mock/main.go generated vendored Normal file
View File

@@ -0,0 +1,88 @@
/*
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 (
"fmt"
"net"
"os"
"os/signal"
"strings"
"syscall"
"github.com/kubernetes-csi/csi-test/driver"
"github.com/kubernetes-csi/csi-test/mock/service"
)
func main() {
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()
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

@@ -0,0 +1,16 @@
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

@@ -0,0 +1,559 @@
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/v0"
)
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.Id] = 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 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.Attributes[devPathKey]; device != "" {
var volRo bool
var roVal string
if ro, ok := v.Attributes[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{
PublishInfo: 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.Attributes[devPathKey] = device
v.Attributes[ReadOnlyKey] = roVal
s.vols[i] = v
return &csi.ControllerPublishVolumeResponse{
PublishInfo: map[string]string{
"device": device,
"readonly": roVal,
},
}, nil
}
func (s *service) ControllerUnpublishVolume(
ctx context.Context,
req *csi.ControllerUnpublishVolumeRequest) (
*csi.ControllerUnpublishVolumeResponse, error) {
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.Attributes[devPathKey] == "" {
return &csi.ControllerUnpublishVolumeResponse{}, nil
}
// Unpublish the volume.
delete(v.Attributes, devPathKey)
delete(v.Attributes, 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{
Supported: true,
}, 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) {
return &csi.ControllerGetCapabilitiesResponse{
Capabilities: []*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_PUBLISH_UNPUBLISH_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,
},
},
},
},
}, 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.
snapshots := s.snapshots.List(csi.SnapshotStatus_READY)
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

@@ -0,0 +1,48 @@
package service
import (
"golang.org/x/net/context"
"github.com/container-storage-interface/spec/lib/go/csi/v0"
"github.com/golang/protobuf/ptypes/wrappers"
)
func (s *service) GetPluginInfo(
ctx context.Context,
req *csi.GetPluginInfoRequest) (
*csi.GetPluginInfoResponse, error) {
return &csi.GetPluginInfoResponse{
Name: Name,
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

@@ -0,0 +1,236 @@
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/v0"
)
func (s *service) NodeStageVolume(
ctx context.Context,
req *csi.NodeStageVolumeRequest) (
*csi.NodeStageVolumeResponse, error) {
device, ok := req.PublishInfo["device"]
if !ok {
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.Attributes[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.Attributes[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.Attributes[nodeStgPathKey] == "" {
return &csi.NodeUnstageVolumeResponse{}, nil
}
// Unpublish the volume.
delete(v.Attributes, 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.PublishInfo["device"]
if !ok {
return nil, status.Error(
codes.InvalidArgument,
"publish 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.Attributes[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.Attributes[nodeMntPathKey] = req.GetStagingTargetPath()
} else {
v.Attributes[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.Attributes[nodeMntPathKey] == "" {
return &csi.NodeUnpublishVolumeResponse{}, nil
}
// Unpublish the volume.
delete(v.Attributes, nodeMntPathKey)
s.vols[i] = v
return &csi.NodeUnpublishVolumeResponse{}, nil
}
func (s *service) NodeGetId(
ctx context.Context,
req *csi.NodeGetIdRequest) (
*csi.NodeGetIdResponse, error) {
return &csi.NodeGetIdResponse{
NodeId: s.nodeID,
}, 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) {
return &csi.NodeGetInfoResponse{
NodeId: s.nodeID,
}, nil
}

View File

@@ -0,0 +1,137 @@
package service
import (
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/container-storage-interface/spec/lib/go/csi/v0"
"github.com/kubernetes-csi/csi-test/mock/cache"
"golang.org/x/net/context"
)
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",
}
// 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
}
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() Service {
s := &service{nodeID: Name}
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{
Id: fmt.Sprintf("%d", atomic.AddUint64(&s.volsNID, 1)),
Attributes: 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.Id) {
return i, vi
}
case "name":
if n, ok := vi.Attributes["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 {
return cache.Snapshot{
Name: name,
Parameters: parameters,
SnapshotCSI: csi.Snapshot{
Id: fmt.Sprintf("%d", atomic.AddUint64(&s.snapshotsNID, 1)),
CreatedAt: time.Now().UnixNano(),
SourceVolumeId: sourceVolumeId,
Status: &csi.SnapshotStatus{
Type: csi.SnapshotStatus_READY,
Details: "snapshot ready",
},
},
}
}