2021-05-16 01:15:43 +03:00
|
|
|
// Copyright (c) Vitaliy Filippov, 2019+
|
|
|
|
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
|
|
|
|
|
|
|
package vitastor
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2023-03-17 11:11:52 +03:00
|
|
|
"fmt"
|
2021-05-16 01:15:43 +03:00
|
|
|
"strings"
|
|
|
|
"bytes"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"io/ioutil"
|
|
|
|
|
|
|
|
"github.com/kubernetes-csi/csi-lib-utils/protosanitizer"
|
|
|
|
"k8s.io/klog"
|
|
|
|
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
2023-10-30 18:43:43 +03:00
|
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
2021-05-16 01:15:43 +03:00
|
|
|
|
|
|
|
"github.com/container-storage-interface/spec/lib/go/csi"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
KB int64 = 1024
|
|
|
|
MB int64 = 1024 * KB
|
|
|
|
GB int64 = 1024 * MB
|
|
|
|
TB int64 = 1024 * GB
|
|
|
|
ETCD_TIMEOUT time.Duration = 15*time.Second
|
|
|
|
)
|
|
|
|
|
|
|
|
type InodeIndex struct
|
|
|
|
{
|
|
|
|
Id uint64 `json:"id"`
|
|
|
|
PoolId uint64 `json:"pool_id"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type InodeConfig struct
|
|
|
|
{
|
|
|
|
Name string `json:"name"`
|
|
|
|
Size uint64 `json:"size,omitempty"`
|
|
|
|
ParentPool uint64 `json:"parent_pool,omitempty"`
|
|
|
|
ParentId uint64 `json:"parent_id,omitempty"`
|
|
|
|
Readonly bool `json:"readonly,omitempty"`
|
2023-10-30 18:43:43 +03:00
|
|
|
CreateTs uint64 `json:"create_ts,omitempty"`
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type ControllerServer struct
|
|
|
|
{
|
|
|
|
*Driver
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewControllerServer create new instance controller
|
|
|
|
func NewControllerServer(driver *Driver) *ControllerServer
|
|
|
|
{
|
|
|
|
return &ControllerServer{
|
|
|
|
Driver: driver,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-02 11:49:00 +03:00
|
|
|
func GetConnectionParams(params map[string]string) (map[string]string, error)
|
2021-05-16 01:15:43 +03:00
|
|
|
{
|
|
|
|
ctxVars := make(map[string]string)
|
|
|
|
configPath := params["configPath"]
|
|
|
|
if (configPath == "")
|
|
|
|
{
|
|
|
|
configPath = "/etc/vitastor/vitastor.conf"
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ctxVars["configPath"] = configPath
|
|
|
|
}
|
|
|
|
config := make(map[string]interface{})
|
2023-12-02 11:49:00 +03:00
|
|
|
configFD, err := os.Open(configPath)
|
|
|
|
if (err != nil)
|
2021-05-16 01:15:43 +03:00
|
|
|
{
|
2023-12-02 11:49:00 +03:00
|
|
|
return nil, err
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
2023-12-02 11:49:00 +03:00
|
|
|
defer configFD.Close()
|
|
|
|
data, _ := ioutil.ReadAll(configFD)
|
|
|
|
json.Unmarshal(data, &config)
|
|
|
|
// Check etcd URL in the config, but do not use the explicit etcdUrl
|
|
|
|
// parameter for CLI calls, otherwise users won't be able to later
|
|
|
|
// change them - storage class parameters are saved in volume IDs
|
2021-05-16 01:15:43 +03:00
|
|
|
var etcdUrl []string
|
2023-12-02 11:49:00 +03:00
|
|
|
switch config["etcd_address"].(type)
|
2021-05-16 01:15:43 +03:00
|
|
|
{
|
2023-12-02 11:49:00 +03:00
|
|
|
case string:
|
2023-12-02 13:47:51 +03:00
|
|
|
url := strings.TrimSpace(config["etcd_address"].(string))
|
2023-12-02 11:49:00 +03:00
|
|
|
if (url != "")
|
2021-05-16 01:15:43 +03:00
|
|
|
{
|
2023-12-02 11:49:00 +03:00
|
|
|
etcdUrl = strings.Split(url, ",")
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
2023-12-02 11:49:00 +03:00
|
|
|
case []string:
|
|
|
|
etcdUrl = config["etcd_address"].([]string)
|
2023-12-06 00:28:18 +03:00
|
|
|
case []interface{}:
|
|
|
|
for _, url := range config["etcd_address"].([]interface{})
|
|
|
|
{
|
|
|
|
s, ok := url.(string)
|
|
|
|
if (ok)
|
|
|
|
{
|
|
|
|
etcdUrl = append(etcdUrl, s)
|
|
|
|
}
|
|
|
|
}
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
2023-12-02 11:49:00 +03:00
|
|
|
if (len(etcdUrl) == 0)
|
2021-05-16 01:15:43 +03:00
|
|
|
{
|
2023-12-02 11:49:00 +03:00
|
|
|
return nil, status.Error(codes.InvalidArgument, "etcd_address is missing in "+configPath)
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
2023-12-02 11:49:00 +03:00
|
|
|
return ctxVars, nil
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
|
|
|
|
2023-12-06 01:01:14 +03:00
|
|
|
func system(program string, args ...string) ([]byte, []byte, error)
|
2022-04-17 19:38:35 +03:00
|
|
|
{
|
2023-12-06 00:48:12 +03:00
|
|
|
klog.Infof("Running "+program+" "+strings.Join(args, " "))
|
2023-12-02 13:47:51 +03:00
|
|
|
c := exec.Command(program, args...)
|
2022-04-17 19:38:35 +03:00
|
|
|
var stdout, stderr bytes.Buffer
|
2023-12-02 13:47:51 +03:00
|
|
|
c.Stdout, c.Stderr = &stdout, &stderr
|
2022-04-17 19:38:35 +03:00
|
|
|
err := c.Run()
|
|
|
|
if (err != nil)
|
|
|
|
{
|
2023-12-02 13:47:51 +03:00
|
|
|
stdoutStr, stderrStr := string(stdout.Bytes()), string(stderr.Bytes())
|
|
|
|
klog.Errorf(program+" "+strings.Join(args, " ")+" failed: %s, status %s\n", stdoutStr+stderrStr, err)
|
2023-12-06 01:01:14 +03:00
|
|
|
return nil, nil, status.Error(codes.Internal, stdoutStr+stderrStr+" (status "+err.Error()+")")
|
2022-04-17 19:38:35 +03:00
|
|
|
}
|
2023-12-06 01:01:14 +03:00
|
|
|
return stdout.Bytes(), stderr.Bytes(), nil
|
2022-04-17 19:38:35 +03:00
|
|
|
}
|
|
|
|
|
2023-12-02 13:47:51 +03:00
|
|
|
func invokeCLI(ctxVars map[string]string, args []string) ([]byte, error)
|
|
|
|
{
|
|
|
|
if (ctxVars["configPath"] != "")
|
|
|
|
{
|
|
|
|
args = append(args, "--config_path", ctxVars["configPath"])
|
|
|
|
}
|
2023-12-06 01:01:14 +03:00
|
|
|
stdout, _, err := system("/usr/bin/vitastor-cli", args...)
|
|
|
|
return stdout, err
|
2023-12-02 13:47:51 +03:00
|
|
|
}
|
|
|
|
|
2021-05-16 01:15:43 +03:00
|
|
|
// Create the volume
|
|
|
|
func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error)
|
|
|
|
{
|
|
|
|
klog.Infof("received controller create volume request %+v", protosanitizer.StripSecrets(req))
|
|
|
|
if (req == nil)
|
|
|
|
{
|
|
|
|
return nil, status.Errorf(codes.InvalidArgument, "request cannot be empty")
|
|
|
|
}
|
|
|
|
if (req.GetName() == "")
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "name is a required field")
|
|
|
|
}
|
|
|
|
volumeCapabilities := req.GetVolumeCapabilities()
|
|
|
|
if (volumeCapabilities == nil)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "volume capabilities is a required field")
|
|
|
|
}
|
|
|
|
|
|
|
|
etcdVolumePrefix := req.Parameters["etcdVolumePrefix"]
|
|
|
|
poolId, _ := strconv.ParseUint(req.Parameters["poolId"], 10, 64)
|
|
|
|
if (poolId == 0)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "poolId is missing in storage class configuration")
|
|
|
|
}
|
|
|
|
|
|
|
|
volName := etcdVolumePrefix + req.GetName()
|
|
|
|
volSize := 1 * GB
|
|
|
|
if capRange := req.GetCapacityRange(); capRange != nil
|
|
|
|
{
|
|
|
|
volSize = ((capRange.GetRequiredBytes() + MB - 1) / MB) * MB
|
|
|
|
}
|
|
|
|
|
2023-12-02 11:49:00 +03:00
|
|
|
ctxVars, err := GetConnectionParams(req.Parameters)
|
|
|
|
if (err != nil)
|
2021-05-16 01:15:43 +03:00
|
|
|
{
|
2023-12-02 11:49:00 +03:00
|
|
|
return nil, err
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
|
|
|
|
2023-10-30 18:43:43 +03:00
|
|
|
args := []string{ "create", volName, "-s", fmt.Sprintf("%v", volSize), "--pool", fmt.Sprintf("%v", poolId) }
|
|
|
|
|
|
|
|
// Support creation from snapshot
|
|
|
|
var src *csi.VolumeContentSource
|
|
|
|
if (req.VolumeContentSource.GetSnapshot() != nil)
|
|
|
|
{
|
|
|
|
snapId := req.VolumeContentSource.GetSnapshot().GetSnapshotId()
|
|
|
|
if (snapId != "")
|
|
|
|
{
|
|
|
|
snapVars := make(map[string]string)
|
|
|
|
err := json.Unmarshal([]byte(snapId), &snapVars)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Internal, "volume ID not in JSON format")
|
|
|
|
}
|
|
|
|
args = append(args, "--parent", snapVars["name"]+"@"+snapVars["snapshot"])
|
|
|
|
src = &csi.VolumeContentSource{
|
|
|
|
Type: &csi.VolumeContentSource_Snapshot{
|
|
|
|
Snapshot: &csi.VolumeContentSource_SnapshotSource{
|
|
|
|
SnapshotId: snapId,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-17 19:38:35 +03:00
|
|
|
// Create image using vitastor-cli
|
2023-12-02 11:49:00 +03:00
|
|
|
_, err = invokeCLI(ctxVars, args)
|
2021-05-16 01:15:43 +03:00
|
|
|
if (err != nil)
|
|
|
|
{
|
2022-04-17 19:38:35 +03:00
|
|
|
if (strings.Index(err.Error(), "already exists") > 0)
|
2021-05-16 01:15:43 +03:00
|
|
|
{
|
2023-10-30 18:43:43 +03:00
|
|
|
inodeCfg, err := invokeList(ctxVars, volName, true)
|
2021-05-16 01:15:43 +03:00
|
|
|
if (err != nil)
|
|
|
|
{
|
2022-04-17 19:38:35 +03:00
|
|
|
return nil, err
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
2022-04-17 19:38:35 +03:00
|
|
|
if (inodeCfg[0].Size < uint64(volSize))
|
2021-05-16 01:15:43 +03:00
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Internal, "image "+volName+" is already created, but size is less than expected")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-04-17 19:38:35 +03:00
|
|
|
return nil, err
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ctxVars["name"] = volName
|
|
|
|
volumeIdJson, _ := json.Marshal(ctxVars)
|
|
|
|
return &csi.CreateVolumeResponse{
|
|
|
|
Volume: &csi.Volume{
|
|
|
|
// Ugly, but VolumeContext isn't passed to DeleteVolume :-(
|
|
|
|
VolumeId: string(volumeIdJson),
|
|
|
|
CapacityBytes: volSize,
|
2023-10-30 18:43:43 +03:00
|
|
|
ContentSource: src,
|
2021-05-16 01:15:43 +03:00
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteVolume deletes the given volume
|
|
|
|
func (cs *ControllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error)
|
|
|
|
{
|
|
|
|
klog.Infof("received controller delete volume request %+v", protosanitizer.StripSecrets(req))
|
|
|
|
if (req == nil)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "request cannot be empty")
|
|
|
|
}
|
|
|
|
|
2023-10-30 18:43:43 +03:00
|
|
|
volVars := make(map[string]string)
|
|
|
|
err := json.Unmarshal([]byte(req.VolumeId), &volVars)
|
2021-05-16 01:15:43 +03:00
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Internal, "volume ID not in JSON format")
|
|
|
|
}
|
2023-10-30 18:43:43 +03:00
|
|
|
volName := volVars["name"]
|
2021-05-16 01:15:43 +03:00
|
|
|
|
2023-12-02 11:49:00 +03:00
|
|
|
ctxVars, err := GetConnectionParams(volVars)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-05-16 01:15:43 +03:00
|
|
|
|
2022-04-17 19:38:35 +03:00
|
|
|
_, err = invokeCLI(ctxVars, []string{ "rm", volName })
|
2021-05-16 01:15:43 +03:00
|
|
|
if (err != nil)
|
|
|
|
{
|
2022-04-17 19:38:35 +03:00
|
|
|
return nil, err
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return &csi.DeleteVolumeResponse{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ControllerPublishVolume return Unimplemented error
|
|
|
|
func (cs *ControllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
// ControllerUnpublishVolume return Unimplemented error
|
|
|
|
func (cs *ControllerServer) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
// ValidateVolumeCapabilities checks whether the volume capabilities requested are supported.
|
|
|
|
func (cs *ControllerServer) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error)
|
|
|
|
{
|
|
|
|
klog.Infof("received controller validate volume capability request %+v", protosanitizer.StripSecrets(req))
|
|
|
|
if (req == nil)
|
|
|
|
{
|
|
|
|
return nil, status.Errorf(codes.InvalidArgument, "request is nil")
|
|
|
|
}
|
|
|
|
volumeID := req.GetVolumeId()
|
|
|
|
if (volumeID == "")
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "volumeId is nil")
|
|
|
|
}
|
|
|
|
volumeCapabilities := req.GetVolumeCapabilities()
|
|
|
|
if (volumeCapabilities == nil)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "volumeCapabilities is nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
var volumeCapabilityAccessModes []*csi.VolumeCapability_AccessMode
|
|
|
|
for _, mode := range []csi.VolumeCapability_AccessMode_Mode{
|
|
|
|
csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
|
|
|
|
csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
|
|
|
|
} {
|
|
|
|
volumeCapabilityAccessModes = append(volumeCapabilityAccessModes, &csi.VolumeCapability_AccessMode{Mode: mode})
|
|
|
|
}
|
|
|
|
|
|
|
|
capabilitySupport := false
|
|
|
|
for _, capability := range volumeCapabilities
|
|
|
|
{
|
|
|
|
for _, volumeCapabilityAccessMode := range volumeCapabilityAccessModes
|
|
|
|
{
|
|
|
|
if (volumeCapabilityAccessMode.Mode == capability.AccessMode.Mode)
|
|
|
|
{
|
|
|
|
capabilitySupport = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!capabilitySupport)
|
|
|
|
{
|
|
|
|
return nil, status.Errorf(codes.NotFound, "%v not supported", req.GetVolumeCapabilities())
|
|
|
|
}
|
|
|
|
|
|
|
|
return &csi.ValidateVolumeCapabilitiesResponse{
|
|
|
|
Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{
|
|
|
|
VolumeCapabilities: req.VolumeCapabilities,
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListVolumes returns a list of volumes
|
|
|
|
func (cs *ControllerServer) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetCapacity returns the capacity of the storage pool
|
|
|
|
func (cs *ControllerServer) GetCapacity(ctx context.Context, req *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
// ControllerGetCapabilities returns the capabilities of the controller service.
|
|
|
|
func (cs *ControllerServer) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error)
|
|
|
|
{
|
|
|
|
functionControllerServerCapabilities := func(cap csi.ControllerServiceCapability_RPC_Type) *csi.ControllerServiceCapability
|
|
|
|
{
|
|
|
|
return &csi.ControllerServiceCapability{
|
|
|
|
Type: &csi.ControllerServiceCapability_Rpc{
|
|
|
|
Rpc: &csi.ControllerServiceCapability_RPC{
|
|
|
|
Type: cap,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var controllerServerCapabilities []*csi.ControllerServiceCapability
|
|
|
|
for _, capability := range []csi.ControllerServiceCapability_RPC_Type{
|
|
|
|
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
|
|
|
|
csi.ControllerServiceCapability_RPC_LIST_VOLUMES,
|
2023-10-31 00:50:08 +03:00
|
|
|
csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
|
2021-05-16 01:15:43 +03:00
|
|
|
csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
|
2023-10-30 18:43:43 +03:00
|
|
|
csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
|
|
|
|
// TODO: csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
|
2021-05-16 01:15:43 +03:00
|
|
|
} {
|
|
|
|
controllerServerCapabilities = append(controllerServerCapabilities, functionControllerServerCapabilities(capability))
|
|
|
|
}
|
|
|
|
|
|
|
|
return &csi.ControllerGetCapabilitiesResponse{
|
|
|
|
Capabilities: controllerServerCapabilities,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-10-30 18:43:43 +03:00
|
|
|
func invokeList(ctxVars map[string]string, pattern string, expectExist bool) ([]InodeConfig, error)
|
|
|
|
{
|
|
|
|
stat, err := invokeCLI(ctxVars, []string{ "ls", "--json", pattern })
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var inodeCfg []InodeConfig
|
|
|
|
err = json.Unmarshal(stat, &inodeCfg)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Internal, "Invalid JSON in vitastor-cli ls: "+err.Error())
|
|
|
|
}
|
|
|
|
if (expectExist && len(inodeCfg) == 0)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Internal, "Can't find expected image "+pattern+" via vitastor-cli ls")
|
|
|
|
}
|
|
|
|
return inodeCfg, nil
|
|
|
|
}
|
|
|
|
|
2021-05-16 01:15:43 +03:00
|
|
|
// CreateSnapshot create snapshot of an existing PV
|
|
|
|
func (cs *ControllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error)
|
|
|
|
{
|
2023-10-30 18:43:43 +03:00
|
|
|
klog.Infof("received controller create snapshot request %+v", protosanitizer.StripSecrets(req))
|
|
|
|
if (req == nil)
|
|
|
|
{
|
|
|
|
return nil, status.Errorf(codes.InvalidArgument, "request cannot be empty")
|
|
|
|
}
|
|
|
|
if (req.SourceVolumeId == "" || req.Name == "")
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "source volume ID and snapshot name are required fields")
|
|
|
|
}
|
|
|
|
|
|
|
|
// snapshot name
|
|
|
|
snapName := req.Name
|
|
|
|
|
|
|
|
// req.VolumeId is an ugly json string in our case :)
|
|
|
|
ctxVars := make(map[string]string)
|
|
|
|
err := json.Unmarshal([]byte(req.SourceVolumeId), &ctxVars)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Internal, "volume ID not in JSON format")
|
|
|
|
}
|
|
|
|
volName := ctxVars["name"]
|
|
|
|
|
|
|
|
// Create image using vitastor-cli
|
|
|
|
_, err = invokeCLI(ctxVars, []string{ "create", "--snapshot", snapName, volName })
|
|
|
|
if (err != nil && strings.Index(err.Error(), "already exists") <= 0)
|
|
|
|
{
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check created snapshot
|
|
|
|
inodeCfg, err := invokeList(ctxVars, volName+"@"+snapName, true)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use ugly JSON snapshot ID again, DeleteSnapshot doesn't have context :-(
|
|
|
|
ctxVars["snapshot"] = snapName
|
|
|
|
snapIdJson, _ := json.Marshal(ctxVars)
|
|
|
|
return &csi.CreateSnapshotResponse{
|
|
|
|
Snapshot: &csi.Snapshot{
|
|
|
|
SizeBytes: int64(inodeCfg[0].Size),
|
|
|
|
SnapshotId: string(snapIdJson),
|
|
|
|
SourceVolumeId: req.SourceVolumeId,
|
|
|
|
CreationTime: ×tamppb.Timestamp{ Seconds: int64(inodeCfg[0].CreateTs) },
|
|
|
|
ReadyToUse: true,
|
|
|
|
},
|
|
|
|
}, nil
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteSnapshot delete provided snapshot of a PV
|
|
|
|
func (cs *ControllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error)
|
|
|
|
{
|
2023-10-30 18:43:43 +03:00
|
|
|
klog.Infof("received controller delete snapshot request %+v", protosanitizer.StripSecrets(req))
|
|
|
|
if (req == nil)
|
|
|
|
{
|
|
|
|
return nil, status.Errorf(codes.InvalidArgument, "request cannot be empty")
|
|
|
|
}
|
|
|
|
if (req.SnapshotId == "")
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "snapshot ID is a required field")
|
|
|
|
}
|
|
|
|
|
|
|
|
volVars := make(map[string]string)
|
|
|
|
err := json.Unmarshal([]byte(req.SnapshotId), &volVars)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Internal, "snapshot ID not in JSON format")
|
|
|
|
}
|
|
|
|
volName := volVars["name"]
|
|
|
|
snapName := volVars["snapshot"]
|
|
|
|
|
2023-12-02 11:49:00 +03:00
|
|
|
ctxVars, err := GetConnectionParams(volVars)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-10-30 18:43:43 +03:00
|
|
|
|
|
|
|
_, err = invokeCLI(ctxVars, []string{ "rm", volName+"@"+snapName })
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &csi.DeleteSnapshotResponse{}, nil
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// ListSnapshots list the snapshots of a PV
|
|
|
|
func (cs *ControllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error)
|
|
|
|
{
|
2023-10-30 18:43:43 +03:00
|
|
|
klog.Infof("received controller list snapshots request %+v", protosanitizer.StripSecrets(req))
|
|
|
|
if (req == nil)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "request cannot be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
volVars := make(map[string]string)
|
|
|
|
err := json.Unmarshal([]byte(req.SourceVolumeId), &volVars)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Internal, "volume ID not in JSON format")
|
|
|
|
}
|
|
|
|
volName := volVars["name"]
|
2023-12-02 11:49:00 +03:00
|
|
|
ctxVars, err := GetConnectionParams(volVars)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-10-30 18:43:43 +03:00
|
|
|
|
|
|
|
inodeCfg, err := invokeList(ctxVars, volName+"@*", false)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
resp := &csi.ListSnapshotsResponse{}
|
|
|
|
for _, ino := range inodeCfg
|
|
|
|
{
|
|
|
|
snapName := ino.Name[len(volName)+1:]
|
|
|
|
if (len(req.StartingToken) > 0 && snapName < req.StartingToken)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
else if (req.MaxEntries == 0 || len(resp.Entries) < int(req.MaxEntries))
|
|
|
|
{
|
|
|
|
volVars["snapshot"] = snapName
|
|
|
|
snapIdJson, _ := json.Marshal(volVars)
|
|
|
|
resp.Entries = append(resp.Entries, &csi.ListSnapshotsResponse_Entry{
|
|
|
|
Snapshot: &csi.Snapshot{
|
|
|
|
SizeBytes: int64(ino.Size),
|
|
|
|
SnapshotId: string(snapIdJson),
|
|
|
|
SourceVolumeId: req.SourceVolumeId,
|
|
|
|
CreationTime: ×tamppb.Timestamp{ Seconds: int64(ino.CreateTs) },
|
|
|
|
ReadyToUse: true,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
resp.NextToken = snapName
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
|
|
|
|
2023-10-31 00:50:08 +03:00
|
|
|
// ControllerExpandVolume increases the size of a volume
|
2021-05-16 01:15:43 +03:00
|
|
|
func (cs *ControllerServer) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error)
|
|
|
|
{
|
2023-10-31 00:50:08 +03:00
|
|
|
klog.Infof("received controller expand volume request %+v", protosanitizer.StripSecrets(req))
|
|
|
|
if (req == nil)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "request cannot be empty")
|
|
|
|
}
|
|
|
|
if (req.VolumeId == "" || req.CapacityRange == nil || req.CapacityRange.RequiredBytes == 0)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "VolumeId, CapacityRange and RequiredBytes are required fields")
|
|
|
|
}
|
|
|
|
|
|
|
|
volVars := make(map[string]string)
|
|
|
|
err := json.Unmarshal([]byte(req.VolumeId), &volVars)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Internal, "volume ID not in JSON format")
|
|
|
|
}
|
|
|
|
volName := volVars["name"]
|
2023-12-02 11:49:00 +03:00
|
|
|
ctxVars, err := GetConnectionParams(volVars)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-10-31 00:50:08 +03:00
|
|
|
|
|
|
|
inodeCfg, err := invokeList(ctxVars, volName, true)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req.CapacityRange.RequiredBytes > 0 && inodeCfg[0].Size < uint64(req.CapacityRange.RequiredBytes))
|
|
|
|
{
|
|
|
|
sz := ((req.CapacityRange.RequiredBytes+4095)/4096)*4096
|
|
|
|
_, err := invokeCLI(ctxVars, []string{ "modify", "--inc_size", "1", "--resize", fmt.Sprintf("%d", sz), volName })
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
inodeCfg, err = invokeList(ctxVars, volName, true)
|
|
|
|
if (err != nil)
|
|
|
|
{
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &csi.ControllerExpandVolumeResponse{
|
|
|
|
CapacityBytes: int64(inodeCfg[0].Size),
|
|
|
|
NodeExpansionRequired: false,
|
|
|
|
}, nil
|
2021-05-16 01:15:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// ControllerGetVolume get volume info
|
|
|
|
func (cs *ControllerServer) ControllerGetVolume(ctx context.Context, req *csi.ControllerGetVolumeRequest) (*csi.ControllerGetVolumeResponse, error)
|
|
|
|
{
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
}
|