mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
e2e: import csi-test mock driver
This is a verbatim copy of the corresponding files in csi-test v4.0.2. They'll be modified in future commits to make the code usable when embedded in e2e.test. Some of those changes may be worthwhile backporting to csi-test, but this is uncertain at this time.
This commit is contained in:
parent
21ffdd1a28
commit
7f2b438020
110
test/e2e/storage/drivers/csi-test/driver/driver-controller.go
Normal file
110
test/e2e/storage/drivers/csi-test/driver/driver-controller.go
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
Copyright 2019 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 driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/reflection"
|
||||
|
||||
csi "github.com/container-storage-interface/spec/lib/go/csi"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// CSIDriverControllerServer is the Controller service component of the driver.
|
||||
type CSIDriverControllerServer struct {
|
||||
Controller csi.ControllerServer
|
||||
Identity csi.IdentityServer
|
||||
}
|
||||
|
||||
// CSIDriverController is the CSI Driver Controller backend.
|
||||
type CSIDriverController struct {
|
||||
listener net.Listener
|
||||
server *grpc.Server
|
||||
controllerServer *CSIDriverControllerServer
|
||||
wg sync.WaitGroup
|
||||
running bool
|
||||
lock sync.Mutex
|
||||
creds *CSICreds
|
||||
}
|
||||
|
||||
func NewCSIDriverController(controllerServer *CSIDriverControllerServer) *CSIDriverController {
|
||||
return &CSIDriverController{
|
||||
controllerServer: controllerServer,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CSIDriverController) goServe(started chan<- bool) {
|
||||
goServe(c.server, &c.wg, c.listener, started)
|
||||
}
|
||||
|
||||
func (c *CSIDriverController) Address() string {
|
||||
return c.listener.Addr().String()
|
||||
}
|
||||
|
||||
func (c *CSIDriverController) Start(l net.Listener) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// Set listener.
|
||||
c.listener = l
|
||||
|
||||
// Create a new grpc server.
|
||||
c.server = grpc.NewServer(
|
||||
grpc.UnaryInterceptor(c.callInterceptor),
|
||||
)
|
||||
|
||||
if c.controllerServer.Controller != nil {
|
||||
csi.RegisterControllerServer(c.server, c.controllerServer.Controller)
|
||||
}
|
||||
if c.controllerServer.Identity != nil {
|
||||
csi.RegisterIdentityServer(c.server, c.controllerServer.Identity)
|
||||
}
|
||||
|
||||
reflection.Register(c.server)
|
||||
|
||||
waitForServer := make(chan bool)
|
||||
c.goServe(waitForServer)
|
||||
<-waitForServer
|
||||
c.running = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CSIDriverController) Stop() {
|
||||
stop(&c.lock, &c.wg, c.server, c.running)
|
||||
}
|
||||
|
||||
func (c *CSIDriverController) Close() {
|
||||
c.server.Stop()
|
||||
}
|
||||
|
||||
func (c *CSIDriverController) IsRunning() bool {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
return c.running
|
||||
}
|
||||
|
||||
func (c *CSIDriverController) SetDefaultCreds() {
|
||||
setDefaultCreds(c.creds)
|
||||
}
|
||||
|
||||
func (c *CSIDriverController) callInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
return callInterceptor(ctx, c.creds, req, info, handler)
|
||||
}
|
109
test/e2e/storage/drivers/csi-test/driver/driver-node.go
Normal file
109
test/e2e/storage/drivers/csi-test/driver/driver-node.go
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
Copyright 2019 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 driver
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
csi "github.com/container-storage-interface/spec/lib/go/csi"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
)
|
||||
|
||||
// CSIDriverNodeServer is the Node service component of the driver.
|
||||
type CSIDriverNodeServer struct {
|
||||
Node csi.NodeServer
|
||||
Identity csi.IdentityServer
|
||||
}
|
||||
|
||||
// CSIDriverNode is the CSI Driver Node backend.
|
||||
type CSIDriverNode struct {
|
||||
listener net.Listener
|
||||
server *grpc.Server
|
||||
nodeServer *CSIDriverNodeServer
|
||||
wg sync.WaitGroup
|
||||
running bool
|
||||
lock sync.Mutex
|
||||
creds *CSICreds
|
||||
}
|
||||
|
||||
func NewCSIDriverNode(nodeServer *CSIDriverNodeServer) *CSIDriverNode {
|
||||
return &CSIDriverNode{
|
||||
nodeServer: nodeServer,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CSIDriverNode) goServe(started chan<- bool) {
|
||||
goServe(c.server, &c.wg, c.listener, started)
|
||||
}
|
||||
|
||||
func (c *CSIDriverNode) Address() string {
|
||||
return c.listener.Addr().String()
|
||||
}
|
||||
|
||||
func (c *CSIDriverNode) Start(l net.Listener) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// Set listener.
|
||||
c.listener = l
|
||||
|
||||
// Create a new grpc server.
|
||||
c.server = grpc.NewServer(
|
||||
grpc.UnaryInterceptor(c.callInterceptor),
|
||||
)
|
||||
|
||||
if c.nodeServer.Node != nil {
|
||||
csi.RegisterNodeServer(c.server, c.nodeServer.Node)
|
||||
}
|
||||
if c.nodeServer.Identity != nil {
|
||||
csi.RegisterIdentityServer(c.server, c.nodeServer.Identity)
|
||||
}
|
||||
|
||||
reflection.Register(c.server)
|
||||
|
||||
waitForServer := make(chan bool)
|
||||
c.goServe(waitForServer)
|
||||
<-waitForServer
|
||||
c.running = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CSIDriverNode) Stop() {
|
||||
stop(&c.lock, &c.wg, c.server, c.running)
|
||||
}
|
||||
|
||||
func (c *CSIDriverNode) Close() {
|
||||
c.server.Stop()
|
||||
}
|
||||
|
||||
func (c *CSIDriverNode) IsRunning() bool {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
return c.running
|
||||
}
|
||||
|
||||
func (c *CSIDriverNode) SetDefaultCreds() {
|
||||
setDefaultCreds(c.creds)
|
||||
}
|
||||
|
||||
func (c *CSIDriverNode) callInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
return callInterceptor(ctx, c.creds, req, info, handler)
|
||||
}
|
312
test/e2e/storage/drivers/csi-test/driver/driver.go
Normal file
312
test/e2e/storage/drivers/csi-test/driver/driver.go
Normal file
@ -0,0 +1,312 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
//go:generate mockgen -package=driver -destination=driver.mock.go github.com/container-storage-interface/spec/lib/go/csi IdentityServer,ControllerServer,NodeServer
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/container-storage-interface/spec/lib/go/csi"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoCredentials is the error when a secret is enabled but not passed in the request.
|
||||
ErrNoCredentials = errors.New("secret must be provided")
|
||||
// ErrAuthFailed is the error when the secret is incorrect.
|
||||
ErrAuthFailed = errors.New("authentication failed")
|
||||
)
|
||||
|
||||
// CSIDriverServers is a unified driver component with both Controller and Node
|
||||
// services.
|
||||
type CSIDriverServers struct {
|
||||
Controller csi.ControllerServer
|
||||
Identity csi.IdentityServer
|
||||
Node csi.NodeServer
|
||||
}
|
||||
|
||||
// This is the key name in all the CSI secret objects.
|
||||
const secretField = "secretKey"
|
||||
|
||||
// CSICreds is a driver specific secret type. Drivers can have a key-val pair of
|
||||
// secrets. This mock driver has a single string secret with secretField as the
|
||||
// key.
|
||||
type CSICreds struct {
|
||||
CreateVolumeSecret string
|
||||
DeleteVolumeSecret string
|
||||
ControllerPublishVolumeSecret string
|
||||
ControllerUnpublishVolumeSecret string
|
||||
NodeStageVolumeSecret string
|
||||
NodePublishVolumeSecret string
|
||||
CreateSnapshotSecret string
|
||||
DeleteSnapshotSecret string
|
||||
ControllerValidateVolumeCapabilitiesSecret string
|
||||
}
|
||||
|
||||
type CSIDriver struct {
|
||||
listener net.Listener
|
||||
server *grpc.Server
|
||||
servers *CSIDriverServers
|
||||
wg sync.WaitGroup
|
||||
running bool
|
||||
lock sync.Mutex
|
||||
creds *CSICreds
|
||||
}
|
||||
|
||||
func NewCSIDriver(servers *CSIDriverServers) *CSIDriver {
|
||||
return &CSIDriver{
|
||||
servers: servers,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CSIDriver) goServe(started chan<- bool) {
|
||||
goServe(c.server, &c.wg, c.listener, started)
|
||||
}
|
||||
|
||||
func (c *CSIDriver) Address() string {
|
||||
return c.listener.Addr().String()
|
||||
}
|
||||
func (c *CSIDriver) Start(l net.Listener) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// Set listener
|
||||
c.listener = l
|
||||
|
||||
// Create a new grpc server
|
||||
c.server = grpc.NewServer(
|
||||
grpc.UnaryInterceptor(c.callInterceptor),
|
||||
)
|
||||
|
||||
// Register Mock servers
|
||||
if c.servers.Controller != nil {
|
||||
csi.RegisterControllerServer(c.server, c.servers.Controller)
|
||||
}
|
||||
if c.servers.Identity != nil {
|
||||
csi.RegisterIdentityServer(c.server, c.servers.Identity)
|
||||
}
|
||||
if c.servers.Node != nil {
|
||||
csi.RegisterNodeServer(c.server, c.servers.Node)
|
||||
}
|
||||
reflection.Register(c.server)
|
||||
|
||||
// Start listening for requests
|
||||
waitForServer := make(chan bool)
|
||||
c.goServe(waitForServer)
|
||||
<-waitForServer
|
||||
c.running = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CSIDriver) Stop() {
|
||||
stop(&c.lock, &c.wg, c.server, c.running)
|
||||
}
|
||||
|
||||
func (c *CSIDriver) Close() {
|
||||
c.server.Stop()
|
||||
}
|
||||
|
||||
func (c *CSIDriver) IsRunning() bool {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
return c.running
|
||||
}
|
||||
|
||||
// SetDefaultCreds sets the default secrets for CSI creds.
|
||||
func (c *CSIDriver) SetDefaultCreds() {
|
||||
setDefaultCreds(c.creds)
|
||||
}
|
||||
|
||||
func (c *CSIDriver) callInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
return callInterceptor(ctx, c.creds, req, info, handler)
|
||||
}
|
||||
|
||||
// goServe starts a grpc server.
|
||||
func goServe(server *grpc.Server, wg *sync.WaitGroup, listener net.Listener, started chan<- bool) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
started <- true
|
||||
err := server.Serve(listener)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// stop stops a grpc server.
|
||||
func stop(lock *sync.Mutex, wg *sync.WaitGroup, server *grpc.Server, running bool) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if !running {
|
||||
return
|
||||
}
|
||||
|
||||
server.Stop()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// setDefaultCreds sets the default credentials, given a CSICreds instance.
|
||||
func setDefaultCreds(creds *CSICreds) {
|
||||
creds = &CSICreds{
|
||||
CreateVolumeSecret: "secretval1",
|
||||
DeleteVolumeSecret: "secretval2",
|
||||
ControllerPublishVolumeSecret: "secretval3",
|
||||
ControllerUnpublishVolumeSecret: "secretval4",
|
||||
NodeStageVolumeSecret: "secretval5",
|
||||
NodePublishVolumeSecret: "secretval6",
|
||||
CreateSnapshotSecret: "secretval7",
|
||||
DeleteSnapshotSecret: "secretval8",
|
||||
ControllerValidateVolumeCapabilitiesSecret: "secretval9",
|
||||
}
|
||||
}
|
||||
|
||||
func callInterceptor(ctx context.Context, creds *CSICreds, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
err := authInterceptor(creds, req)
|
||||
if err != nil {
|
||||
logGRPC(info.FullMethod, req, nil, err)
|
||||
return nil, err
|
||||
}
|
||||
rsp, err := handler(ctx, req)
|
||||
logGRPC(info.FullMethod, req, rsp, err)
|
||||
return rsp, err
|
||||
}
|
||||
|
||||
func authInterceptor(creds *CSICreds, req interface{}) error {
|
||||
if creds != nil {
|
||||
authenticated, authErr := isAuthenticated(req, creds)
|
||||
if !authenticated {
|
||||
if authErr == ErrNoCredentials {
|
||||
return status.Error(codes.InvalidArgument, authErr.Error())
|
||||
}
|
||||
if authErr == ErrAuthFailed {
|
||||
return status.Error(codes.Unauthenticated, authErr.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func logGRPC(method string, request, reply interface{}, err error) {
|
||||
// Log JSON with the request and response for easier parsing
|
||||
logMessage := struct {
|
||||
Method string
|
||||
Request interface{}
|
||||
Response interface{}
|
||||
// Error as string, for backward compatibility.
|
||||
// "" on no error.
|
||||
Error string
|
||||
// Full error dump, to be able to parse out full gRPC error code and message separately in a test.
|
||||
FullError error
|
||||
}{
|
||||
Method: method,
|
||||
Request: request,
|
||||
Response: reply,
|
||||
FullError: err,
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logMessage.Error = err.Error()
|
||||
}
|
||||
|
||||
msg, _ := json.Marshal(logMessage)
|
||||
klog.V(3).Infof("gRPCCall: %s\n", msg)
|
||||
}
|
||||
|
||||
func isAuthenticated(req interface{}, creds *CSICreds) (bool, error) {
|
||||
switch r := req.(type) {
|
||||
case *csi.CreateVolumeRequest:
|
||||
return authenticateCreateVolume(r, creds)
|
||||
case *csi.DeleteVolumeRequest:
|
||||
return authenticateDeleteVolume(r, creds)
|
||||
case *csi.ControllerPublishVolumeRequest:
|
||||
return authenticateControllerPublishVolume(r, creds)
|
||||
case *csi.ControllerUnpublishVolumeRequest:
|
||||
return authenticateControllerUnpublishVolume(r, creds)
|
||||
case *csi.NodeStageVolumeRequest:
|
||||
return authenticateNodeStageVolume(r, creds)
|
||||
case *csi.NodePublishVolumeRequest:
|
||||
return authenticateNodePublishVolume(r, creds)
|
||||
case *csi.CreateSnapshotRequest:
|
||||
return authenticateCreateSnapshot(r, creds)
|
||||
case *csi.DeleteSnapshotRequest:
|
||||
return authenticateDeleteSnapshot(r, creds)
|
||||
case *csi.ValidateVolumeCapabilitiesRequest:
|
||||
return authenticateControllerValidateVolumeCapabilities(r, creds)
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func authenticateCreateVolume(req *csi.CreateVolumeRequest, creds *CSICreds) (bool, error) {
|
||||
return credsCheck(req.GetSecrets(), creds.CreateVolumeSecret)
|
||||
}
|
||||
|
||||
func authenticateDeleteVolume(req *csi.DeleteVolumeRequest, creds *CSICreds) (bool, error) {
|
||||
return credsCheck(req.GetSecrets(), creds.DeleteVolumeSecret)
|
||||
}
|
||||
|
||||
func authenticateControllerPublishVolume(req *csi.ControllerPublishVolumeRequest, creds *CSICreds) (bool, error) {
|
||||
return credsCheck(req.GetSecrets(), creds.ControllerPublishVolumeSecret)
|
||||
}
|
||||
|
||||
func authenticateControllerUnpublishVolume(req *csi.ControllerUnpublishVolumeRequest, creds *CSICreds) (bool, error) {
|
||||
return credsCheck(req.GetSecrets(), creds.ControllerUnpublishVolumeSecret)
|
||||
}
|
||||
|
||||
func authenticateNodeStageVolume(req *csi.NodeStageVolumeRequest, creds *CSICreds) (bool, error) {
|
||||
return credsCheck(req.GetSecrets(), creds.NodeStageVolumeSecret)
|
||||
}
|
||||
|
||||
func authenticateNodePublishVolume(req *csi.NodePublishVolumeRequest, creds *CSICreds) (bool, error) {
|
||||
return credsCheck(req.GetSecrets(), creds.NodePublishVolumeSecret)
|
||||
}
|
||||
|
||||
func authenticateCreateSnapshot(req *csi.CreateSnapshotRequest, creds *CSICreds) (bool, error) {
|
||||
return credsCheck(req.GetSecrets(), creds.CreateSnapshotSecret)
|
||||
}
|
||||
|
||||
func authenticateDeleteSnapshot(req *csi.DeleteSnapshotRequest, creds *CSICreds) (bool, error) {
|
||||
return credsCheck(req.GetSecrets(), creds.DeleteSnapshotSecret)
|
||||
}
|
||||
|
||||
func authenticateControllerValidateVolumeCapabilities(req *csi.ValidateVolumeCapabilitiesRequest, creds *CSICreds) (bool, error) {
|
||||
return credsCheck(req.GetSecrets(), creds.ControllerValidateVolumeCapabilitiesSecret)
|
||||
}
|
||||
|
||||
func credsCheck(secrets map[string]string, secretVal string) (bool, error) {
|
||||
if len(secrets) == 0 {
|
||||
return false, ErrNoCredentials
|
||||
}
|
||||
|
||||
if secrets[secretField] != secretVal {
|
||||
return false, ErrAuthFailed
|
||||
}
|
||||
return true, nil
|
||||
}
|
392
test/e2e/storage/drivers/csi-test/driver/driver.mock.go
Normal file
392
test/e2e/storage/drivers/csi-test/driver/driver.mock.go
Normal file
@ -0,0 +1,392 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/container-storage-interface/spec/lib/go/csi (interfaces: IdentityServer,ControllerServer,NodeServer)
|
||||
|
||||
// Package driver is a generated GoMock package.
|
||||
package driver
|
||||
|
||||
import (
|
||||
context "context"
|
||||
csi "github.com/container-storage-interface/spec/lib/go/csi"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockIdentityServer is a mock of IdentityServer interface
|
||||
type MockIdentityServer struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockIdentityServerMockRecorder
|
||||
}
|
||||
|
||||
// MockIdentityServerMockRecorder is the mock recorder for MockIdentityServer
|
||||
type MockIdentityServerMockRecorder struct {
|
||||
mock *MockIdentityServer
|
||||
}
|
||||
|
||||
// NewMockIdentityServer creates a new mock instance
|
||||
func NewMockIdentityServer(ctrl *gomock.Controller) *MockIdentityServer {
|
||||
mock := &MockIdentityServer{ctrl: ctrl}
|
||||
mock.recorder = &MockIdentityServerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockIdentityServer) EXPECT() *MockIdentityServerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetPluginCapabilities mocks base method
|
||||
func (m *MockIdentityServer) GetPluginCapabilities(arg0 context.Context, arg1 *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) {
|
||||
ret := m.ctrl.Call(m, "GetPluginCapabilities", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.GetPluginCapabilitiesResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetPluginCapabilities indicates an expected call of GetPluginCapabilities
|
||||
func (mr *MockIdentityServerMockRecorder) GetPluginCapabilities(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPluginCapabilities", reflect.TypeOf((*MockIdentityServer)(nil).GetPluginCapabilities), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetPluginInfo mocks base method
|
||||
func (m *MockIdentityServer) GetPluginInfo(arg0 context.Context, arg1 *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) {
|
||||
ret := m.ctrl.Call(m, "GetPluginInfo", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.GetPluginInfoResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetPluginInfo indicates an expected call of GetPluginInfo
|
||||
func (mr *MockIdentityServerMockRecorder) GetPluginInfo(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPluginInfo", reflect.TypeOf((*MockIdentityServer)(nil).GetPluginInfo), arg0, arg1)
|
||||
}
|
||||
|
||||
// Probe mocks base method
|
||||
func (m *MockIdentityServer) Probe(arg0 context.Context, arg1 *csi.ProbeRequest) (*csi.ProbeResponse, error) {
|
||||
ret := m.ctrl.Call(m, "Probe", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.ProbeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Probe indicates an expected call of Probe
|
||||
func (mr *MockIdentityServerMockRecorder) Probe(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Probe", reflect.TypeOf((*MockIdentityServer)(nil).Probe), arg0, arg1)
|
||||
}
|
||||
|
||||
// MockControllerServer is a mock of ControllerServer interface
|
||||
type MockControllerServer struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockControllerServerMockRecorder
|
||||
}
|
||||
|
||||
// MockControllerServerMockRecorder is the mock recorder for MockControllerServer
|
||||
type MockControllerServerMockRecorder struct {
|
||||
mock *MockControllerServer
|
||||
}
|
||||
|
||||
// NewMockControllerServer creates a new mock instance
|
||||
func NewMockControllerServer(ctrl *gomock.Controller) *MockControllerServer {
|
||||
mock := &MockControllerServer{ctrl: ctrl}
|
||||
mock.recorder = &MockControllerServerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockControllerServer) EXPECT() *MockControllerServerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// ControllerExpandVolume mocks base method
|
||||
func (m *MockControllerServer) ControllerExpandVolume(arg0 context.Context, arg1 *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) {
|
||||
ret := m.ctrl.Call(m, "ControllerExpandVolume", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.ControllerExpandVolumeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ControllerExpandVolume indicates an expected call of ControllerExpandVolume
|
||||
func (mr *MockControllerServerMockRecorder) ControllerExpandVolume(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ControllerExpandVolume", reflect.TypeOf((*MockControllerServer)(nil).ControllerExpandVolume), arg0, arg1)
|
||||
}
|
||||
|
||||
// ControllerGetCapabilities mocks base method
|
||||
func (m *MockControllerServer) ControllerGetCapabilities(arg0 context.Context, arg1 *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) {
|
||||
ret := m.ctrl.Call(m, "ControllerGetCapabilities", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.ControllerGetCapabilitiesResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ControllerGetCapabilities indicates an expected call of ControllerGetCapabilities
|
||||
func (mr *MockControllerServerMockRecorder) ControllerGetCapabilities(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ControllerGetCapabilities", reflect.TypeOf((*MockControllerServer)(nil).ControllerGetCapabilities), arg0, arg1)
|
||||
}
|
||||
|
||||
// ControllerPublishVolume mocks base method
|
||||
func (m *MockControllerServer) ControllerPublishVolume(arg0 context.Context, arg1 *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {
|
||||
ret := m.ctrl.Call(m, "ControllerPublishVolume", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.ControllerPublishVolumeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ControllerPublishVolume indicates an expected call of ControllerPublishVolume
|
||||
func (mr *MockControllerServerMockRecorder) ControllerPublishVolume(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ControllerPublishVolume", reflect.TypeOf((*MockControllerServer)(nil).ControllerPublishVolume), arg0, arg1)
|
||||
}
|
||||
|
||||
// ControllerUnpublishVolume mocks base method
|
||||
func (m *MockControllerServer) ControllerUnpublishVolume(arg0 context.Context, arg1 *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) {
|
||||
ret := m.ctrl.Call(m, "ControllerUnpublishVolume", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.ControllerUnpublishVolumeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ControllerUnpublishVolume indicates an expected call of ControllerUnpublishVolume
|
||||
func (mr *MockControllerServerMockRecorder) ControllerUnpublishVolume(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ControllerUnpublishVolume", reflect.TypeOf((*MockControllerServer)(nil).ControllerUnpublishVolume), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateSnapshot mocks base method
|
||||
func (m *MockControllerServer) CreateSnapshot(arg0 context.Context, arg1 *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) {
|
||||
ret := m.ctrl.Call(m, "CreateSnapshot", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.CreateSnapshotResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateSnapshot indicates an expected call of CreateSnapshot
|
||||
func (mr *MockControllerServerMockRecorder) CreateSnapshot(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSnapshot", reflect.TypeOf((*MockControllerServer)(nil).CreateSnapshot), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateVolume mocks base method
|
||||
func (m *MockControllerServer) CreateVolume(arg0 context.Context, arg1 *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
|
||||
ret := m.ctrl.Call(m, "CreateVolume", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.CreateVolumeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateVolume indicates an expected call of CreateVolume
|
||||
func (mr *MockControllerServerMockRecorder) CreateVolume(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateVolume", reflect.TypeOf((*MockControllerServer)(nil).CreateVolume), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteSnapshot mocks base method
|
||||
func (m *MockControllerServer) DeleteSnapshot(arg0 context.Context, arg1 *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) {
|
||||
ret := m.ctrl.Call(m, "DeleteSnapshot", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.DeleteSnapshotResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DeleteSnapshot indicates an expected call of DeleteSnapshot
|
||||
func (mr *MockControllerServerMockRecorder) DeleteSnapshot(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSnapshot", reflect.TypeOf((*MockControllerServer)(nil).DeleteSnapshot), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteVolume mocks base method
|
||||
func (m *MockControllerServer) DeleteVolume(arg0 context.Context, arg1 *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
|
||||
ret := m.ctrl.Call(m, "DeleteVolume", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.DeleteVolumeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DeleteVolume indicates an expected call of DeleteVolume
|
||||
func (mr *MockControllerServerMockRecorder) DeleteVolume(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteVolume", reflect.TypeOf((*MockControllerServer)(nil).DeleteVolume), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetCapacity mocks base method
|
||||
func (m *MockControllerServer) GetCapacity(arg0 context.Context, arg1 *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) {
|
||||
ret := m.ctrl.Call(m, "GetCapacity", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.GetCapacityResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetCapacity indicates an expected call of GetCapacity
|
||||
func (mr *MockControllerServerMockRecorder) GetCapacity(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCapacity", reflect.TypeOf((*MockControllerServer)(nil).GetCapacity), arg0, arg1)
|
||||
}
|
||||
|
||||
// ListSnapshots mocks base method
|
||||
func (m *MockControllerServer) ListSnapshots(arg0 context.Context, arg1 *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
|
||||
ret := m.ctrl.Call(m, "ListSnapshots", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.ListSnapshotsResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ListSnapshots indicates an expected call of ListSnapshots
|
||||
func (mr *MockControllerServerMockRecorder) ListSnapshots(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSnapshots", reflect.TypeOf((*MockControllerServer)(nil).ListSnapshots), arg0, arg1)
|
||||
}
|
||||
|
||||
// ListVolumes mocks base method
|
||||
func (m *MockControllerServer) ListVolumes(arg0 context.Context, arg1 *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) {
|
||||
ret := m.ctrl.Call(m, "ListVolumes", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.ListVolumesResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (m *MockControllerServer) ControllerGetVolume(arg0 context.Context, arg1 *csi.ControllerGetVolumeRequest) (*csi.ControllerGetVolumeResponse, error) {
|
||||
ret := m.ctrl.Call(m, "ControllerGetVolume", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.ControllerGetVolumeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ControllerGetVolume indicates an expected call of ControllerGetVolume
|
||||
func (mr *MockControllerServerMockRecorder) ControllerGetVolume(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ControllerGetVolume", reflect.TypeOf((*MockControllerServer)(nil).ControllerGetVolume), arg0, arg1)
|
||||
}
|
||||
|
||||
// ListVolumes indicates an expected call of ListVolumes
|
||||
func (mr *MockControllerServerMockRecorder) ListVolumes(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListVolumes", reflect.TypeOf((*MockControllerServer)(nil).ListVolumes), arg0, arg1)
|
||||
}
|
||||
|
||||
// ValidateVolumeCapabilities mocks base method
|
||||
func (m *MockControllerServer) ValidateVolumeCapabilities(arg0 context.Context, arg1 *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
|
||||
ret := m.ctrl.Call(m, "ValidateVolumeCapabilities", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.ValidateVolumeCapabilitiesResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ValidateVolumeCapabilities indicates an expected call of ValidateVolumeCapabilities
|
||||
func (mr *MockControllerServerMockRecorder) ValidateVolumeCapabilities(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateVolumeCapabilities", reflect.TypeOf((*MockControllerServer)(nil).ValidateVolumeCapabilities), arg0, arg1)
|
||||
}
|
||||
|
||||
// MockNodeServer is a mock of NodeServer interface
|
||||
type MockNodeServer struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockNodeServerMockRecorder
|
||||
}
|
||||
|
||||
// MockNodeServerMockRecorder is the mock recorder for MockNodeServer
|
||||
type MockNodeServerMockRecorder struct {
|
||||
mock *MockNodeServer
|
||||
}
|
||||
|
||||
// NewMockNodeServer creates a new mock instance
|
||||
func NewMockNodeServer(ctrl *gomock.Controller) *MockNodeServer {
|
||||
mock := &MockNodeServer{ctrl: ctrl}
|
||||
mock.recorder = &MockNodeServerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockNodeServer) EXPECT() *MockNodeServerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// NodeExpandVolume mocks base method
|
||||
func (m *MockNodeServer) NodeExpandVolume(arg0 context.Context, arg1 *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) {
|
||||
ret := m.ctrl.Call(m, "NodeExpandVolume", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.NodeExpandVolumeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// NodeExpandVolume indicates an expected call of NodeExpandVolume
|
||||
func (mr *MockNodeServerMockRecorder) NodeExpandVolume(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeExpandVolume", reflect.TypeOf((*MockNodeServer)(nil).NodeExpandVolume), arg0, arg1)
|
||||
}
|
||||
|
||||
// NodeGetCapabilities mocks base method
|
||||
func (m *MockNodeServer) NodeGetCapabilities(arg0 context.Context, arg1 *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) {
|
||||
ret := m.ctrl.Call(m, "NodeGetCapabilities", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.NodeGetCapabilitiesResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// NodeGetCapabilities indicates an expected call of NodeGetCapabilities
|
||||
func (mr *MockNodeServerMockRecorder) NodeGetCapabilities(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeGetCapabilities", reflect.TypeOf((*MockNodeServer)(nil).NodeGetCapabilities), arg0, arg1)
|
||||
}
|
||||
|
||||
// NodeGetInfo mocks base method
|
||||
func (m *MockNodeServer) NodeGetInfo(arg0 context.Context, arg1 *csi.NodeGetInfoRequest) (*csi.NodeGetInfoResponse, error) {
|
||||
ret := m.ctrl.Call(m, "NodeGetInfo", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.NodeGetInfoResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// NodeGetInfo indicates an expected call of NodeGetInfo
|
||||
func (mr *MockNodeServerMockRecorder) NodeGetInfo(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeGetInfo", reflect.TypeOf((*MockNodeServer)(nil).NodeGetInfo), arg0, arg1)
|
||||
}
|
||||
|
||||
// NodeGetVolumeStats mocks base method
|
||||
func (m *MockNodeServer) NodeGetVolumeStats(arg0 context.Context, arg1 *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) {
|
||||
ret := m.ctrl.Call(m, "NodeGetVolumeStats", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.NodeGetVolumeStatsResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// NodeGetVolumeStats indicates an expected call of NodeGetVolumeStats
|
||||
func (mr *MockNodeServerMockRecorder) NodeGetVolumeStats(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeGetVolumeStats", reflect.TypeOf((*MockNodeServer)(nil).NodeGetVolumeStats), arg0, arg1)
|
||||
}
|
||||
|
||||
// NodePublishVolume mocks base method
|
||||
func (m *MockNodeServer) NodePublishVolume(arg0 context.Context, arg1 *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
|
||||
ret := m.ctrl.Call(m, "NodePublishVolume", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.NodePublishVolumeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// NodePublishVolume indicates an expected call of NodePublishVolume
|
||||
func (mr *MockNodeServerMockRecorder) NodePublishVolume(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodePublishVolume", reflect.TypeOf((*MockNodeServer)(nil).NodePublishVolume), arg0, arg1)
|
||||
}
|
||||
|
||||
// NodeStageVolume mocks base method
|
||||
func (m *MockNodeServer) NodeStageVolume(arg0 context.Context, arg1 *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) {
|
||||
ret := m.ctrl.Call(m, "NodeStageVolume", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.NodeStageVolumeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// NodeStageVolume indicates an expected call of NodeStageVolume
|
||||
func (mr *MockNodeServerMockRecorder) NodeStageVolume(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeStageVolume", reflect.TypeOf((*MockNodeServer)(nil).NodeStageVolume), arg0, arg1)
|
||||
}
|
||||
|
||||
// NodeUnpublishVolume mocks base method
|
||||
func (m *MockNodeServer) NodeUnpublishVolume(arg0 context.Context, arg1 *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) {
|
||||
ret := m.ctrl.Call(m, "NodeUnpublishVolume", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.NodeUnpublishVolumeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// NodeUnpublishVolume indicates an expected call of NodeUnpublishVolume
|
||||
func (mr *MockNodeServerMockRecorder) NodeUnpublishVolume(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeUnpublishVolume", reflect.TypeOf((*MockNodeServer)(nil).NodeUnpublishVolume), arg0, arg1)
|
||||
}
|
||||
|
||||
// NodeUnstageVolume mocks base method
|
||||
func (m *MockNodeServer) NodeUnstageVolume(arg0 context.Context, arg1 *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) {
|
||||
ret := m.ctrl.Call(m, "NodeUnstageVolume", arg0, arg1)
|
||||
ret0, _ := ret[0].(*csi.NodeUnstageVolumeResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// NodeUnstageVolume indicates an expected call of NodeUnstageVolume
|
||||
func (mr *MockNodeServerMockRecorder) NodeUnstageVolume(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeUnstageVolume", reflect.TypeOf((*MockNodeServer)(nil).NodeUnstageVolume), arg0, arg1)
|
||||
}
|
89
test/e2e/storage/drivers/csi-test/driver/mock.go
Normal file
89
test/e2e/storage/drivers/csi-test/driver/mock.go
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
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 driver
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/kubernetes-csi/csi-test/v4/utils"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type MockCSIDriverServers struct {
|
||||
Controller *MockControllerServer
|
||||
Identity *MockIdentityServer
|
||||
Node *MockNodeServer
|
||||
}
|
||||
|
||||
type MockCSIDriver struct {
|
||||
CSIDriver
|
||||
conn *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewMockCSIDriver(servers *MockCSIDriverServers) *MockCSIDriver {
|
||||
return &MockCSIDriver{
|
||||
CSIDriver: CSIDriver{
|
||||
servers: &CSIDriverServers{
|
||||
Controller: servers.Controller,
|
||||
Node: servers.Node,
|
||||
Identity: servers.Identity,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// StartOnAddress starts a new gRPC server listening on given address.
|
||||
func (m *MockCSIDriver) StartOnAddress(network, address string) error {
|
||||
l, err := net.Listen(network, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.CSIDriver.Start(l); err != nil {
|
||||
l.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts a new gRPC server listening on a random TCP loopback port.
|
||||
func (m *MockCSIDriver) Start() error {
|
||||
// Listen on a port assigned by the net package
|
||||
return m.StartOnAddress("tcp", "127.0.0.1:0")
|
||||
}
|
||||
|
||||
func (m *MockCSIDriver) Nexus() (*grpc.ClientConn, error) {
|
||||
// Start server
|
||||
err := m.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a client connection
|
||||
m.conn, err = utils.Connect(m.Address(), grpc.WithInsecure())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.conn, nil
|
||||
}
|
||||
|
||||
func (m *MockCSIDriver) Close() {
|
||||
m.conn.Close()
|
||||
m.server.Stop()
|
||||
}
|
89
test/e2e/storage/drivers/csi-test/mock/cache/SnapshotCache.go
vendored
Normal file
89
test/e2e/storage/drivers/csi-test/mock/cache/SnapshotCache.go
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
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{}
|
||||
}
|
834
test/e2e/storage/drivers/csi-test/mock/service/controller.go
Normal file
834
test/e2e/storage/drivers/csi-test/mock/service/controller.go
Normal file
@ -0,0 +1,834 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"path"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/container-storage-interface/spec/lib/go/csi"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
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")
|
||||
}
|
||||
if hookVal, hookMsg := s.execHook("CreateVolumeStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
|
||||
var v csi.Volume
|
||||
// Create volume from content source if provided.
|
||||
if req.GetVolumeContentSource() != nil {
|
||||
switch req.GetVolumeContentSource().GetType().(type) {
|
||||
case *csi.VolumeContentSource_Snapshot:
|
||||
sid := req.GetVolumeContentSource().GetSnapshot().GetSnapshotId()
|
||||
// Check if the source snapshot exists.
|
||||
if snapID, _ := s.snapshots.FindSnapshot("id", sid); snapID >= 0 {
|
||||
v = s.newVolumeFromSnapshot(req.Name, capacity, snapID)
|
||||
} else {
|
||||
return nil, status.Errorf(codes.NotFound, "Requested source snapshot %s not found", sid)
|
||||
}
|
||||
case *csi.VolumeContentSource_Volume:
|
||||
vid := req.GetVolumeContentSource().GetVolume().GetVolumeId()
|
||||
// Check if the source volume exists.
|
||||
if volID, _ := s.findVolNoLock("id", vid); volID >= 0 {
|
||||
v = s.newVolumeFromVolume(req.Name, capacity, volID)
|
||||
} else {
|
||||
return nil, status.Errorf(codes.NotFound, "Requested source volume %s not found", vid)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
v = s.newVolume(req.Name, capacity)
|
||||
}
|
||||
|
||||
// Add the created volume to the service's in-mem volume slice.
|
||||
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: "",
|
||||
}
|
||||
|
||||
if hookVal, hookMsg := s.execHook("CreateVolumeEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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 hookVal, hookMsg := s.execHook("DeleteVolumeStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
||||
if hookVal, hookMsg := s.execHook("DeleteVolumeEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
if hookVal, hookMsg := s.execHook("ControllerPublishVolumeStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Check attach limit before publishing only if attach limit is set.
|
||||
if s.config.AttachLimit > 0 && s.getAttachCount(devPathKey) >= s.config.AttachLimit {
|
||||
return nil, status.Errorf(codes.ResourceExhausted, "Cannot attach any more volumes to this node")
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if volInfo, ok := MockVolumes[req.VolumeId]; ok {
|
||||
volInfo.ISControllerPublished = true
|
||||
MockVolumes[req.VolumeId] = volInfo
|
||||
}
|
||||
|
||||
if hookVal, hookMsg := s.execHook("ControllerPublishVolumeEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if hookVal, hookMsg := s.execHook("ControllerUnpublishVolumeStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
s.volsRWL.Lock()
|
||||
defer s.volsRWL.Unlock()
|
||||
|
||||
i, v := s.findVolNoLock("id", req.VolumeId)
|
||||
if i < 0 {
|
||||
// Not an error: a non-existent volume is not published.
|
||||
// See also https://github.com/kubernetes-csi/external-attacher/pull/165
|
||||
return &csi.ControllerUnpublishVolumeResponse{}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
if hookVal, hookMsg := s.execHook("ControllerUnpublishVolumeEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if hookVal, hookMsg := s.execHook("ValidateVolumeCapabilities"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
return &csi.ValidateVolumeCapabilitiesResponse{
|
||||
Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{
|
||||
VolumeContext: req.GetVolumeContext(),
|
||||
VolumeCapabilities: req.GetVolumeCapabilities(),
|
||||
Parameters: req.GetParameters(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) ControllerGetVolume(
|
||||
ctx context.Context,
|
||||
req *csi.ControllerGetVolumeRequest) (
|
||||
*csi.ControllerGetVolumeResponse, error) {
|
||||
|
||||
if hookVal, hookMsg := s.execHook("GetVolumeStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
resp := &csi.ControllerGetVolumeResponse{
|
||||
Status: &csi.ControllerGetVolumeResponse_VolumeStatus{
|
||||
VolumeCondition: &csi.VolumeCondition{},
|
||||
},
|
||||
}
|
||||
i, v := s.findVolByID(ctx, req.VolumeId)
|
||||
if i < 0 {
|
||||
resp.Status.VolumeCondition.Abnormal = true
|
||||
resp.Status.VolumeCondition.Message = "volume not found"
|
||||
return resp, status.Error(codes.NotFound, req.VolumeId)
|
||||
}
|
||||
|
||||
resp.Volume = &v
|
||||
if !s.config.DisableAttach {
|
||||
resp.Status.PublishedNodeIds = []string{
|
||||
s.nodeID,
|
||||
}
|
||||
}
|
||||
|
||||
if hookVal, hookMsg := s.execHook("GetVolumeEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *service) ListVolumes(
|
||||
ctx context.Context,
|
||||
req *csi.ListVolumesRequest) (
|
||||
*csi.ListVolumesResponse, error) {
|
||||
|
||||
if hookVal, hookMsg := s.execHook("ListVolumesStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
// 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.Aborted,
|
||||
"startingToken=%d !< int32=%d",
|
||||
startingToken, math.MaxUint32)
|
||||
}
|
||||
startingToken = int32(i)
|
||||
}
|
||||
|
||||
if startingToken > ulenVols {
|
||||
return nil, status.Errorf(
|
||||
codes.Aborted,
|
||||
"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++ {
|
||||
volumeStatus := &csi.ListVolumesResponse_VolumeStatus{
|
||||
VolumeCondition: &csi.VolumeCondition{},
|
||||
}
|
||||
|
||||
if !s.config.DisableAttach {
|
||||
volumeStatus.PublishedNodeIds = []string{
|
||||
s.nodeID,
|
||||
}
|
||||
}
|
||||
|
||||
entries[i] = &csi.ListVolumesResponse_Entry{
|
||||
Volume: &vols[j],
|
||||
Status: volumeStatus,
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
var nextToken string
|
||||
if n := startingToken + int32(i); n < ulenVols {
|
||||
nextToken = fmt.Sprintf("%d", n)
|
||||
}
|
||||
|
||||
if hookVal, hookMsg := s.execHook("ListVolumesEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
return &csi.ListVolumesResponse{
|
||||
Entries: entries,
|
||||
NextToken: nextToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) GetCapacity(
|
||||
ctx context.Context,
|
||||
req *csi.GetCapacityRequest) (
|
||||
*csi.GetCapacityResponse, error) {
|
||||
|
||||
if hookVal, hookMsg := s.execHook("GetCapacity"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
return &csi.GetCapacityResponse{
|
||||
AvailableCapacity: MaxStorageCapacity,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) ControllerGetCapabilities(
|
||||
ctx context.Context,
|
||||
req *csi.ControllerGetCapabilitiesRequest) (
|
||||
*csi.ControllerGetCapabilitiesResponse, error) {
|
||||
|
||||
if hookVal, hookMsg := s.execHook("ControllerGetCapabilitiesStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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_LIST_VOLUMES_PUBLISHED_NODES,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &csi.ControllerServiceCapability_Rpc{
|
||||
Rpc: &csi.ControllerServiceCapability_RPC{
|
||||
Type: csi.ControllerServiceCapability_RPC_PUBLISH_READONLY,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &csi.ControllerServiceCapability_Rpc{
|
||||
Rpc: &csi.ControllerServiceCapability_RPC{
|
||||
Type: csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &csi.ControllerServiceCapability_Rpc{
|
||||
Rpc: &csi.ControllerServiceCapability_RPC{
|
||||
Type: csi.ControllerServiceCapability_RPC_GET_VOLUME,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &csi.ControllerServiceCapability_Rpc{
|
||||
Rpc: &csi.ControllerServiceCapability_RPC{
|
||||
Type: csi.ControllerServiceCapability_RPC_VOLUME_CONDITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !s.config.DisableAttach {
|
||||
caps = append(caps, &csi.ControllerServiceCapability{
|
||||
Type: &csi.ControllerServiceCapability_Rpc{
|
||||
Rpc: &csi.ControllerServiceCapability_RPC{
|
||||
Type: csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if !s.config.DisableControllerExpansion {
|
||||
caps = append(caps, &csi.ControllerServiceCapability{
|
||||
Type: &csi.ControllerServiceCapability_Rpc{
|
||||
Rpc: &csi.ControllerServiceCapability_RPC{
|
||||
Type: csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if hookVal, hookMsg := s.execHook("ControllerGetCapabilitiesEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
if hookVal, hookMsg := s.execHook("CreateSnapshotStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
if hookVal, hookMsg := s.execHook("CreateSnapshotEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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 hookVal, hookMsg := s.execHook("DeleteSnapshotStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
||||
if hookVal, hookMsg := s.execHook("DeleteSnapshotEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
return &csi.DeleteSnapshotResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *service) ListSnapshots(ctx context.Context,
|
||||
req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
|
||||
|
||||
if hookVal, hookMsg := s.execHook("ListSnapshots"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
// 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 (s *service) ControllerExpandVolume(
|
||||
ctx context.Context,
|
||||
req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) {
|
||||
if len(req.VolumeId) == 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
|
||||
}
|
||||
|
||||
if req.CapacityRange == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "Request capacity cannot be empty")
|
||||
}
|
||||
|
||||
if hookVal, hookMsg := s.execHook("ControllerExpandVolumeStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if s.config.DisableOnlineExpansion && MockVolumes[v.GetVolumeId()].ISControllerPublished {
|
||||
return nil, status.Error(codes.FailedPrecondition, "volume is published and online volume expansion is not supported")
|
||||
}
|
||||
|
||||
requestBytes := req.CapacityRange.RequiredBytes
|
||||
|
||||
if v.CapacityBytes > requestBytes {
|
||||
return nil, status.Error(codes.InvalidArgument, "cannot change volume capacity to a smaller size")
|
||||
}
|
||||
|
||||
resp := &csi.ControllerExpandVolumeResponse{
|
||||
CapacityBytes: requestBytes,
|
||||
NodeExpansionRequired: s.config.NodeExpansionRequired,
|
||||
}
|
||||
|
||||
// Check to see if the volume already satisfied request size.
|
||||
if v.CapacityBytes == requestBytes {
|
||||
log.WithField("volumeID", v.VolumeId).Infof("Volume capacity is already %d, no need to expand", requestBytes)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Update volume's capacity to the requested size.
|
||||
v.CapacityBytes = requestBytes
|
||||
s.vols[i] = v
|
||||
|
||||
if hookVal, hookMsg := s.execHook("ControllerExpandVolumeEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package service
|
||||
|
||||
// Predefinded constants for the JavaScript hooks, they must correspond to the
|
||||
// error codes used by gRPC, see:
|
||||
// https://github.com/grpc/grpc-go/blob/master/codes/codes.go
|
||||
const (
|
||||
grpcJSCodes string = `OK = 0;
|
||||
CANCELED = 1;
|
||||
UNKNOWN = 2;
|
||||
INVALIDARGUMENT = 3;
|
||||
DEADLINEEXCEEDED = 4;
|
||||
NOTFOUND = 5;
|
||||
ALREADYEXISTS = 6;
|
||||
PERMISSIONDENIED = 7;
|
||||
RESOURCEEXHAUSTED = 8;
|
||||
FAILEDPRECONDITION = 9;
|
||||
ABORTED = 10;
|
||||
OUTOFRANGE = 11;
|
||||
UNIMPLEMENTED = 12;
|
||||
INTERNAL = 13;
|
||||
UNAVAILABLE = 14;
|
||||
DATALOSS = 15;
|
||||
UNAUTHENTICATED = 16`
|
||||
)
|
74
test/e2e/storage/drivers/csi-test/mock/service/identity.go
Normal file
74
test/e2e/storage/drivers/csi-test/mock/service/identity.go
Normal file
@ -0,0 +1,74 @@
|
||||
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) {
|
||||
|
||||
volExpType := csi.PluginCapability_VolumeExpansion_ONLINE
|
||||
|
||||
if s.config.DisableOnlineExpansion {
|
||||
volExpType = csi.PluginCapability_VolumeExpansion_OFFLINE
|
||||
}
|
||||
|
||||
capabilities := []*csi.PluginCapability{
|
||||
{
|
||||
Type: &csi.PluginCapability_Service_{
|
||||
Service: &csi.PluginCapability_Service{
|
||||
Type: csi.PluginCapability_Service_CONTROLLER_SERVICE,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &csi.PluginCapability_VolumeExpansion_{
|
||||
VolumeExpansion: &csi.PluginCapability_VolumeExpansion{
|
||||
Type: volExpType,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if s.config.EnableTopology {
|
||||
capabilities = append(capabilities,
|
||||
&csi.PluginCapability{
|
||||
Type: &csi.PluginCapability_Service_{
|
||||
Service: &csi.PluginCapability_Service{
|
||||
Type: csi.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return &csi.GetPluginCapabilitiesResponse{
|
||||
Capabilities: capabilities,
|
||||
}, nil
|
||||
}
|
460
test/e2e/storage/drivers/csi-test/mock/service/node.go
Normal file
460
test/e2e/storage/drivers/csi-test/mock/service/node.go
Normal file
@ -0,0 +1,460 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"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) {
|
||||
|
||||
if hookVal, hookMsg := s.execHook("NodeStageVolumeStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
exists, err := checkTargetExists(req.StagingTargetPath)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
if !exists {
|
||||
status.Errorf(codes.Internal, "staging target path %s does not exist", req.StagingTargetPath)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if hookVal, hookMsg := s.execHook("NodeStageVolumeEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
if hookVal, hookMsg := s.execHook("NodeUnstageVolumeStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if hookVal, hookMsg := s.execHook("NodeUnstageVolumeEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
return &csi.NodeUnstageVolumeResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *service) NodePublishVolume(
|
||||
ctx context.Context,
|
||||
req *csi.NodePublishVolumeRequest) (
|
||||
*csi.NodePublishVolumeResponse, error) {
|
||||
|
||||
if hookVal, hookMsg := s.execHook("NodePublishVolumeStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
ephemeralVolume := req.GetVolumeContext()["csi.storage.k8s.io/ephemeral"] == "true"
|
||||
device, ok := req.PublishContext["device"]
|
||||
if !ok {
|
||||
if ephemeralVolume || 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")
|
||||
}
|
||||
|
||||
// May happen with old (or, at this time, even the current) Kubernetes
|
||||
// although it shouldn't (https://github.com/kubernetes/kubernetes/issues/75535).
|
||||
exists, err := checkTargetExists(req.TargetPath)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
if !s.config.PermissiveTargetPath && exists {
|
||||
status.Errorf(codes.Internal, "target path %s does exist", req.TargetPath)
|
||||
}
|
||||
|
||||
s.volsRWL.Lock()
|
||||
defer s.volsRWL.Unlock()
|
||||
|
||||
i, v := s.findVolNoLock("id", req.VolumeId)
|
||||
if i < 0 && !ephemeralVolume {
|
||||
return nil, status.Error(codes.NotFound, req.VolumeId)
|
||||
}
|
||||
if i >= 0 && ephemeralVolume {
|
||||
return nil, status.Error(codes.AlreadyExists, 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 ephemeralVolume {
|
||||
MockVolumes[req.VolumeId] = Volume{
|
||||
ISEphemeral: true,
|
||||
}
|
||||
} else {
|
||||
if req.GetTargetPath() != "" {
|
||||
exists, err := checkTargetExists(req.GetTargetPath())
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
if !exists {
|
||||
// If target path does not exist we need to create the directory where volume will be staged
|
||||
if err = os.Mkdir(req.TargetPath, os.FileMode(0755)); err != nil {
|
||||
msg := fmt.Sprintf("NodePublishVolume: could not create target dir %q: %v", req.TargetPath, err)
|
||||
return nil, status.Error(codes.Internal, msg)
|
||||
}
|
||||
}
|
||||
v.VolumeContext[nodeMntPathKey] = req.GetTargetPath()
|
||||
} else {
|
||||
v.VolumeContext[nodeMntPathKey] = device
|
||||
}
|
||||
s.vols[i] = v
|
||||
}
|
||||
if hookVal, hookMsg := s.execHook("NodePublishVolumeEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
if hookVal, hookMsg := s.execHook("NodeUnpublishVolumeStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
s.volsRWL.Lock()
|
||||
defer s.volsRWL.Unlock()
|
||||
|
||||
ephemeralVolume := MockVolumes[req.VolumeId].ISEphemeral
|
||||
i, v := s.findVolNoLock("id", req.VolumeId)
|
||||
if i < 0 && !ephemeralVolume {
|
||||
return nil, status.Error(codes.NotFound, req.VolumeId)
|
||||
}
|
||||
|
||||
if ephemeralVolume {
|
||||
delete(MockVolumes, req.VolumeId)
|
||||
} else {
|
||||
// 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
|
||||
}
|
||||
|
||||
// Delete any created paths
|
||||
err := os.RemoveAll(v.VolumeContext[nodeMntPathKey])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Unable to delete previously created target directory")
|
||||
}
|
||||
|
||||
// Unpublish the volume.
|
||||
delete(v.VolumeContext, nodeMntPathKey)
|
||||
s.vols[i] = v
|
||||
}
|
||||
if hookVal, hookMsg := s.execHook("NodeUnpublishVolumeEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
return &csi.NodeUnpublishVolumeResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *service) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) {
|
||||
if len(req.GetVolumeId()) == 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
|
||||
}
|
||||
if len(req.GetVolumePath()) == 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "Volume Path cannot be empty")
|
||||
}
|
||||
if hookVal, hookMsg := s.execHook("NodeExpandVolumeStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// TODO: NodeExpandVolume MUST be called after successful NodeStageVolume as we has STAGE_UNSTAGE_VOLUME node capacity.
|
||||
resp := &csi.NodeExpandVolumeResponse{}
|
||||
var requestCapacity int64 = 0
|
||||
if req.GetCapacityRange() != nil {
|
||||
requestCapacity = req.CapacityRange.GetRequiredBytes()
|
||||
resp.CapacityBytes = requestCapacity
|
||||
}
|
||||
|
||||
// fsCapacityKey is the key in the volume's attributes that is set to the file system's size.
|
||||
fsCapacityKey := path.Join(s.nodeID, req.GetVolumePath(), "size")
|
||||
// Update volume's fs capacity to requested size.
|
||||
if requestCapacity > 0 {
|
||||
v.VolumeContext[fsCapacityKey] = strconv.FormatInt(requestCapacity, 10)
|
||||
s.vols[i] = v
|
||||
}
|
||||
if hookVal, hookMsg := s.execHook("NodeExpandVolumeEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *service) NodeGetCapabilities(
|
||||
ctx context.Context,
|
||||
req *csi.NodeGetCapabilitiesRequest) (
|
||||
*csi.NodeGetCapabilitiesResponse, error) {
|
||||
|
||||
if hookVal, hookMsg := s.execHook("NodeGetCapabilities"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &csi.NodeServiceCapability_Rpc{
|
||||
Rpc: &csi.NodeServiceCapability_RPC{
|
||||
Type: csi.NodeServiceCapability_RPC_GET_VOLUME_STATS,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &csi.NodeServiceCapability_Rpc{
|
||||
Rpc: &csi.NodeServiceCapability_RPC{
|
||||
Type: csi.NodeServiceCapability_RPC_VOLUME_CONDITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if s.config.NodeExpansionRequired {
|
||||
capabilities = append(capabilities, &csi.NodeServiceCapability{
|
||||
Type: &csi.NodeServiceCapability_Rpc{
|
||||
Rpc: &csi.NodeServiceCapability_RPC{
|
||||
Type: csi.NodeServiceCapability_RPC_EXPAND_VOLUME,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return &csi.NodeGetCapabilitiesResponse{
|
||||
Capabilities: capabilities,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) NodeGetInfo(ctx context.Context,
|
||||
req *csi.NodeGetInfoRequest) (*csi.NodeGetInfoResponse, error) {
|
||||
if hookVal, hookMsg := s.execHook("NodeGetInfo"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
csiNodeResponse := &csi.NodeGetInfoResponse{
|
||||
NodeId: s.nodeID,
|
||||
}
|
||||
if s.config.AttachLimit > 0 {
|
||||
csiNodeResponse.MaxVolumesPerNode = s.config.AttachLimit
|
||||
}
|
||||
if s.config.EnableTopology {
|
||||
csiNodeResponse.AccessibleTopology = &csi.Topology{
|
||||
Segments: map[string]string{
|
||||
TopologyKey: TopologyValue,
|
||||
},
|
||||
}
|
||||
}
|
||||
return csiNodeResponse, nil
|
||||
}
|
||||
|
||||
func (s *service) NodeGetVolumeStats(ctx context.Context,
|
||||
req *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) {
|
||||
|
||||
if hookVal, hookMsg := s.execHook("NodeGetVolumeStatsStart"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
resp := &csi.NodeGetVolumeStatsResponse{
|
||||
VolumeCondition: &csi.VolumeCondition{},
|
||||
}
|
||||
|
||||
if len(req.GetVolumeId()) == 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
|
||||
}
|
||||
|
||||
if len(req.GetVolumePath()) == 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "Volume Path cannot be empty")
|
||||
}
|
||||
|
||||
i, v := s.findVolNoLock("id", req.VolumeId)
|
||||
if i < 0 {
|
||||
resp.VolumeCondition.Abnormal = true
|
||||
resp.VolumeCondition.Message = "Volume not found"
|
||||
return resp, status.Error(codes.NotFound, req.VolumeId)
|
||||
}
|
||||
|
||||
nodeMntPathKey := path.Join(s.nodeID, req.VolumePath)
|
||||
|
||||
_, exists := v.VolumeContext[nodeMntPathKey]
|
||||
if !exists {
|
||||
msg := fmt.Sprintf("volume %q doest not exist on the specified path %q", req.VolumeId, req.VolumePath)
|
||||
resp.VolumeCondition.Abnormal = true
|
||||
resp.VolumeCondition.Message = msg
|
||||
return resp, status.Errorf(codes.NotFound, msg)
|
||||
}
|
||||
|
||||
if hookVal, hookMsg := s.execHook("NodeGetVolumeStatsEnd"); hookVal != codes.OK {
|
||||
return nil, status.Errorf(hookVal, hookMsg)
|
||||
}
|
||||
|
||||
resp.Usage = []*csi.VolumeUsage{
|
||||
{
|
||||
Total: v.GetCapacityBytes(),
|
||||
Unit: csi.VolumeUsage_BYTES,
|
||||
},
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// checkTargetExists checks if a given path exists.
|
||||
func checkTargetExists(targetPath string) (bool, error) {
|
||||
_, err := os.Stat(targetPath)
|
||||
switch {
|
||||
case err == nil:
|
||||
return true, nil
|
||||
case os.IsNotExist(err):
|
||||
return false, nil
|
||||
default:
|
||||
return false, err
|
||||
}
|
||||
}
|
293
test/e2e/storage/drivers/csi-test/mock/service/service.go
Normal file
293
test/e2e/storage/drivers/csi-test/mock/service/service.go
Normal file
@ -0,0 +1,293 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/container-storage-interface/spec/lib/go/csi"
|
||||
"github.com/kubernetes-csi/csi-test/v4/mock/cache"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
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"
|
||||
|
||||
// TopologyKey simulates a per-node topology.
|
||||
TopologyKey = Name + "/node"
|
||||
|
||||
// TopologyValue is the one, fixed node on which the driver runs.
|
||||
TopologyValue = "some-mock-node"
|
||||
)
|
||||
|
||||
// Manifest is the SP's manifest.
|
||||
var Manifest = map[string]string{
|
||||
"url": "https://github.com/kubernetes-csi/csi-test/mock",
|
||||
}
|
||||
|
||||
// JavaScript hooks to be run to perform various tests
|
||||
type Hooks struct {
|
||||
Globals string `yaml:"globals"` // will be executed once before all other scripts
|
||||
CreateVolumeStart string `yaml:"createVolumeStart"`
|
||||
CreateVolumeEnd string `yaml:"createVolumeEnd"`
|
||||
DeleteVolumeStart string `yaml:"deleteVolumeStart"`
|
||||
DeleteVolumeEnd string `yaml:"deleteVolumeEnd"`
|
||||
ControllerPublishVolumeStart string `yaml:"controllerPublishVolumeStart"`
|
||||
ControllerPublishVolumeEnd string `yaml:"controllerPublishVolumeEnd"`
|
||||
ControllerUnpublishVolumeStart string `yaml:"controllerUnpublishVolumeStart"`
|
||||
ControllerUnpublishVolumeEnd string `yaml:"controllerUnpublishVolumeEnd"`
|
||||
ValidateVolumeCapabilities string `yaml:"validateVolumeCapabilities"`
|
||||
ListVolumesStart string `yaml:"listVolumesStart"`
|
||||
ListVolumesEnd string `yaml:"listVolumesEnd"`
|
||||
GetCapacity string `yaml:"getCapacity"`
|
||||
ControllerGetCapabilitiesStart string `yaml:"controllerGetCapabilitiesStart"`
|
||||
ControllerGetCapabilitiesEnd string `yaml:"controllerGetCapabilitiesEnd"`
|
||||
CreateSnapshotStart string `yaml:"createSnapshotStart"`
|
||||
CreateSnapshotEnd string `yaml:"createSnapshotEnd"`
|
||||
DeleteSnapshotStart string `yaml:"deleteSnapshotStart"`
|
||||
DeleteSnapshotEnd string `yaml:"deleteSnapshotEnd"`
|
||||
ListSnapshots string `yaml:"listSnapshots"`
|
||||
ControllerExpandVolumeStart string `yaml:"controllerExpandVolumeStart"`
|
||||
ControllerExpandVolumeEnd string `yaml:"controllerExpandVolumeEnd"`
|
||||
NodeStageVolumeStart string `yaml:"nodeStageVolumeStart"`
|
||||
NodeStageVolumeEnd string `yaml:"nodeStageVolumeEnd"`
|
||||
NodeUnstageVolumeStart string `yaml:"nodeUnstageVolumeStart"`
|
||||
NodeUnstageVolumeEnd string `yaml:"nodeUnstageVolumeEnd"`
|
||||
NodePublishVolumeStart string `yaml:"nodePublishVolumeStart"`
|
||||
NodePublishVolumeEnd string `yaml:"nodePublishVolumeEnd"`
|
||||
NodeUnpublishVolumeStart string `yaml:"nodeUnpublishVolumeStart"`
|
||||
NodeUnpublishVolumeEnd string `yaml:"nodeUnpublishVolumeEnd"`
|
||||
NodeExpandVolumeStart string `yaml:"nodeExpandVolumeStart"`
|
||||
NodeExpandVolumeEnd string `yaml:"nodeExpandVolumeEnd"`
|
||||
NodeGetCapabilities string `yaml:"nodeGetCapabilities"`
|
||||
NodeGetInfo string `yaml:"nodeGetInfo"`
|
||||
NodeGetVolumeStatsStart string `yaml:"nodeGetVolumeStatsStart"`
|
||||
NodeGetVolumeStatsEnd string `yaml:"nodeGetVolumeStatsEnd"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
DisableAttach bool
|
||||
DriverName string
|
||||
AttachLimit int64
|
||||
NodeExpansionRequired bool
|
||||
DisableControllerExpansion bool
|
||||
DisableOnlineExpansion bool
|
||||
PermissiveTargetPath bool
|
||||
EnableTopology bool
|
||||
ExecHooks *Hooks
|
||||
}
|
||||
|
||||
// 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
|
||||
hooksVm *otto.Otto
|
||||
}
|
||||
|
||||
type Volume struct {
|
||||
VolumeCSI csi.Volume
|
||||
NodeID string
|
||||
ISStaged bool
|
||||
ISPublished bool
|
||||
ISEphemeral bool
|
||||
ISControllerPublished 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,
|
||||
}
|
||||
if config.ExecHooks != nil {
|
||||
s.hooksVm = otto.New()
|
||||
s.hooksVm.Run(grpcJSCodes) // set global variables with gRPC error codes
|
||||
_, err := s.hooksVm.Run(s.config.ExecHooks.Globals)
|
||||
if err != nil {
|
||||
klog.Exitf("Error encountered in the global exec hook: %v. Exiting\n", err)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
vol := csi.Volume{
|
||||
VolumeId: fmt.Sprintf("%d", atomic.AddUint64(&s.volsNID, 1)),
|
||||
VolumeContext: map[string]string{"name": name},
|
||||
CapacityBytes: capcity,
|
||||
}
|
||||
s.setTopology(&vol)
|
||||
return vol
|
||||
}
|
||||
|
||||
func (s *service) newVolumeFromSnapshot(name string, capacity int64, snapshotID int) csi.Volume {
|
||||
vol := s.newVolume(name, capacity)
|
||||
vol.ContentSource = &csi.VolumeContentSource{
|
||||
Type: &csi.VolumeContentSource_Snapshot{
|
||||
Snapshot: &csi.VolumeContentSource_SnapshotSource{
|
||||
SnapshotId: fmt.Sprintf("%d", snapshotID),
|
||||
},
|
||||
},
|
||||
}
|
||||
s.setTopology(&vol)
|
||||
return vol
|
||||
}
|
||||
|
||||
func (s *service) newVolumeFromVolume(name string, capacity int64, volumeID int) csi.Volume {
|
||||
vol := s.newVolume(name, capacity)
|
||||
vol.ContentSource = &csi.VolumeContentSource{
|
||||
Type: &csi.VolumeContentSource_Volume{
|
||||
Volume: &csi.VolumeContentSource_VolumeSource{
|
||||
VolumeId: fmt.Sprintf("%d", volumeID),
|
||||
},
|
||||
},
|
||||
}
|
||||
s.setTopology(&vol)
|
||||
return vol
|
||||
}
|
||||
|
||||
func (s *service) setTopology(vol *csi.Volume) {
|
||||
if s.config.EnableTopology {
|
||||
vol.AccessibleTopology = []*csi.Topology{
|
||||
&csi.Topology{
|
||||
Segments: map[string]string{
|
||||
TopologyKey: TopologyValue,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) findVolByID(
|
||||
ctx context.Context, id string) (int, csi.Volume) {
|
||||
|
||||
return s.findVol("id", id)
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// getAttachCount returns the number of attached volumes on the node.
|
||||
func (s *service) getAttachCount(devPathKey string) int64 {
|
||||
var count int64
|
||||
for _, v := range s.vols {
|
||||
if device := v.VolumeContext[devPathKey]; device != "" {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (s *service) execHook(hookName string) (codes.Code, string) {
|
||||
if s.hooksVm != nil {
|
||||
script := reflect.ValueOf(*s.config.ExecHooks).FieldByName(hookName).String()
|
||||
if len(script) > 0 {
|
||||
result, err := s.hooksVm.Run(script)
|
||||
if err != nil {
|
||||
klog.Exitf("Exec hook %s error: %v; exiting\n", hookName, err)
|
||||
}
|
||||
rv, err := result.ToInteger()
|
||||
if err == nil {
|
||||
// Function returned an integer, use it
|
||||
return codes.Code(rv), fmt.Sprintf("Exec hook %s returned non-OK code", hookName)
|
||||
} else {
|
||||
// Function returned non-integer data type, discard it
|
||||
return codes.OK, ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return codes.OK, ""
|
||||
}
|
Loading…
Reference in New Issue
Block a user