2019-09-12 12:32:17 +05:30
|
|
|
/*
|
|
|
|
|
Copyright © 2019 The OpenEBS 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 (
|
|
|
|
|
"fmt"
|
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
|
|
|
"github.com/container-storage-interface/spec/lib/go/csi"
|
|
|
|
|
"github.com/openebs/zfs-localpv/pkg/builder"
|
|
|
|
|
errors "github.com/openebs/zfs-localpv/pkg/common/errors"
|
|
|
|
|
csipayload "github.com/openebs/zfs-localpv/pkg/response"
|
|
|
|
|
zvol "github.com/openebs/zfs-localpv/pkg/zfs"
|
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// controller is the server implementation
|
|
|
|
|
// for CSI Controller
|
|
|
|
|
type controller struct {
|
|
|
|
|
driver *CSIDriver
|
|
|
|
|
capabilities []*csi.ControllerServiceCapability
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewController returns a new instance
|
|
|
|
|
// of CSI controller
|
|
|
|
|
func NewController(d *CSIDriver) csi.ControllerServer {
|
|
|
|
|
return &controller{
|
|
|
|
|
driver: d,
|
|
|
|
|
capabilities: newControllerCapabilities(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SupportedVolumeCapabilityAccessModes contains the list of supported access
|
|
|
|
|
// modes for the volume
|
|
|
|
|
var SupportedVolumeCapabilityAccessModes = []*csi.VolumeCapability_AccessMode{
|
|
|
|
|
&csi.VolumeCapability_AccessMode{
|
|
|
|
|
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CreateVolume provisions a volume
|
|
|
|
|
func (cs *controller) CreateVolume(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
req *csi.CreateVolumeRequest,
|
|
|
|
|
) (*csi.CreateVolumeResponse, error) {
|
|
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
if err = cs.validateVolumeCreateReq(req); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
volName := req.GetName()
|
|
|
|
|
size := req.GetCapacityRange().RequiredBytes
|
|
|
|
|
bs := req.GetParameters()["blocksize"]
|
|
|
|
|
compression := req.GetParameters()["compression"]
|
|
|
|
|
dedup := req.GetParameters()["dedup"]
|
2019-10-15 22:51:48 +05:30
|
|
|
encr := req.GetParameters()["encryption"]
|
|
|
|
|
kf := req.GetParameters()["keyformat"]
|
|
|
|
|
kl := req.GetParameters()["keylocation"]
|
2019-09-12 12:32:17 +05:30
|
|
|
pool := req.GetParameters()["poolname"]
|
|
|
|
|
tp := req.GetParameters()["thinprovision"]
|
2019-11-06 21:20:49 +05:30
|
|
|
schld := req.GetParameters()["scheduler"]
|
2019-09-12 12:32:17 +05:30
|
|
|
|
2019-11-06 21:20:49 +05:30
|
|
|
selected := scheduler(req.AccessibilityRequirements, schld, pool)
|
|
|
|
|
|
|
|
|
|
if len(selected) == 0 {
|
|
|
|
|
return nil, status.Error(codes.Internal, "scheduler failed")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logrus.Infof("scheduled the volume %s/%s on node %s", pool, volName, selected)
|
2019-11-01 06:46:04 +05:30
|
|
|
|
2019-09-12 12:32:17 +05:30
|
|
|
volObj, err := builder.NewBuilder().
|
|
|
|
|
WithName(volName).
|
|
|
|
|
WithCapacity(strconv.FormatInt(int64(size), 10)).
|
|
|
|
|
WithBlockSize(bs).
|
|
|
|
|
WithPoolName(pool).
|
|
|
|
|
WithDedup(dedup).
|
2019-10-15 22:51:48 +05:30
|
|
|
WithEncryption(encr).
|
|
|
|
|
WithKeyFormat(kf).
|
|
|
|
|
WithKeyLocation(kl).
|
2019-09-12 12:32:17 +05:30
|
|
|
WithThinProv(tp).
|
2019-11-06 21:20:49 +05:30
|
|
|
WithOwnerNode(selected).
|
2019-09-12 12:32:17 +05:30
|
|
|
WithCompression(compression).Build()
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = zvol.ProvisionVolume(size, volObj)
|
|
|
|
|
if err != nil {
|
2019-11-06 21:20:49 +05:30
|
|
|
return nil, status.Error(codes.Internal, "not able to provision the volume")
|
2019-09-12 12:32:17 +05:30
|
|
|
}
|
|
|
|
|
|
2019-11-06 21:20:49 +05:30
|
|
|
topology := map[string]string{zvol.ZFSTopologyKey: selected}
|
2019-11-01 06:46:04 +05:30
|
|
|
|
2019-09-12 12:32:17 +05:30
|
|
|
return csipayload.NewCreateVolumeResponseBuilder().
|
|
|
|
|
WithName(volName).
|
|
|
|
|
WithCapacity(size).
|
2019-11-01 06:46:04 +05:30
|
|
|
WithTopology(topology).
|
2019-09-12 12:32:17 +05:30
|
|
|
Build(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DeleteVolume deletes the specified volume
|
|
|
|
|
func (cs *controller) DeleteVolume(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
|
|
|
|
|
|
|
|
|
|
logrus.Infof("received request to delete volume {%s}", req.VolumeId)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
err error
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if err = cs.validateDeleteVolumeReq(req); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
volumeID := req.GetVolumeId()
|
|
|
|
|
|
|
|
|
|
// verify if the volume has already been deleted
|
|
|
|
|
vol, err := zvol.GetVolume(volumeID)
|
|
|
|
|
if vol != nil && vol.DeletionTimestamp != nil {
|
|
|
|
|
goto deleteResponse
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete the corresponding ZV CR
|
|
|
|
|
err = zvol.DeleteVolume(volumeID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrapf(
|
|
|
|
|
err,
|
|
|
|
|
"failed to handle delete volume request for {%s}",
|
|
|
|
|
volumeID,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
deleteResponse:
|
|
|
|
|
return csipayload.NewDeleteVolumeResponseBuilder().Build(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO Implementation will be taken up later
|
|
|
|
|
|
|
|
|
|
// ValidateVolumeCapabilities validates the capabilities
|
|
|
|
|
// required to create a new volume
|
|
|
|
|
// This implements csi.ControllerServer
|
|
|
|
|
func (cs *controller) ValidateVolumeCapabilities(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
req *csi.ValidateVolumeCapabilitiesRequest,
|
|
|
|
|
) (*csi.ValidateVolumeCapabilitiesResponse, error) {
|
|
|
|
|
|
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ControllerGetCapabilities fetches controller capabilities
|
|
|
|
|
//
|
|
|
|
|
// This implements csi.ControllerServer
|
|
|
|
|
func (cs *controller) ControllerGetCapabilities(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
req *csi.ControllerGetCapabilitiesRequest,
|
|
|
|
|
) (*csi.ControllerGetCapabilitiesResponse, error) {
|
|
|
|
|
|
|
|
|
|
resp := &csi.ControllerGetCapabilitiesResponse{
|
|
|
|
|
Capabilities: cs.capabilities,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ControllerExpandVolume resizes previously provisioned volume
|
|
|
|
|
//
|
|
|
|
|
// This implements csi.ControllerServer
|
|
|
|
|
func (cs *controller) ControllerExpandVolume(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
req *csi.ControllerExpandVolumeRequest,
|
|
|
|
|
) (*csi.ControllerExpandVolumeResponse, error) {
|
|
|
|
|
|
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CreateSnapshot creates a snapshot for given volume
|
|
|
|
|
//
|
|
|
|
|
// This implements csi.ControllerServer
|
|
|
|
|
func (cs *controller) CreateSnapshot(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
req *csi.CreateSnapshotRequest,
|
|
|
|
|
) (*csi.CreateSnapshotResponse, error) {
|
|
|
|
|
|
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DeleteSnapshot deletes given snapshot
|
|
|
|
|
//
|
|
|
|
|
// This implements csi.ControllerServer
|
|
|
|
|
func (cs *controller) DeleteSnapshot(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
req *csi.DeleteSnapshotRequest,
|
|
|
|
|
) (*csi.DeleteSnapshotResponse, error) {
|
|
|
|
|
|
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ListSnapshots lists all snapshots for the
|
|
|
|
|
// given volume
|
|
|
|
|
//
|
|
|
|
|
// This implements csi.ControllerServer
|
|
|
|
|
func (cs *controller) ListSnapshots(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
req *csi.ListSnapshotsRequest,
|
|
|
|
|
) (*csi.ListSnapshotsResponse, error) {
|
|
|
|
|
|
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ControllerUnpublishVolume removes a previously
|
|
|
|
|
// attached volume from the given node
|
|
|
|
|
//
|
|
|
|
|
// This implements csi.ControllerServer
|
|
|
|
|
func (cs *controller) ControllerUnpublishVolume(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
req *csi.ControllerUnpublishVolumeRequest,
|
|
|
|
|
) (*csi.ControllerUnpublishVolumeResponse, error) {
|
|
|
|
|
|
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ControllerPublishVolume attaches given volume
|
|
|
|
|
// at the specified node
|
|
|
|
|
//
|
|
|
|
|
// This implements csi.ControllerServer
|
|
|
|
|
func (cs *controller) ControllerPublishVolume(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
req *csi.ControllerPublishVolumeRequest,
|
|
|
|
|
) (*csi.ControllerPublishVolumeResponse, error) {
|
|
|
|
|
|
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetCapacity return the capacity of the
|
|
|
|
|
// given volume
|
|
|
|
|
//
|
|
|
|
|
// This implements csi.ControllerServer
|
|
|
|
|
func (cs *controller) GetCapacity(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
req *csi.GetCapacityRequest,
|
|
|
|
|
) (*csi.GetCapacityResponse, error) {
|
|
|
|
|
|
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ListVolumes lists all the volumes
|
|
|
|
|
//
|
|
|
|
|
// This implements csi.ControllerServer
|
|
|
|
|
func (cs *controller) ListVolumes(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
req *csi.ListVolumesRequest,
|
|
|
|
|
) (*csi.ListVolumesResponse, error) {
|
|
|
|
|
|
|
|
|
|
return nil, status.Error(codes.Unimplemented, "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// validateCapabilities validates if provided capabilities
|
|
|
|
|
// are supported by this driver
|
|
|
|
|
func validateCapabilities(caps []*csi.VolumeCapability) bool {
|
|
|
|
|
|
|
|
|
|
for _, cap := range caps {
|
|
|
|
|
if !IsSupportedVolumeCapabilityAccessMode(cap.AccessMode.Mode) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cs *controller) validateDeleteVolumeReq(req *csi.DeleteVolumeRequest) error {
|
|
|
|
|
volumeID := req.GetVolumeId()
|
|
|
|
|
if volumeID == "" {
|
|
|
|
|
return status.Error(
|
|
|
|
|
codes.InvalidArgument,
|
|
|
|
|
"failed to handle delete volume request: missing volume id",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err := cs.validateRequest(
|
|
|
|
|
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(
|
|
|
|
|
err,
|
|
|
|
|
"failed to handle delete volume request for {%s} : validation failed",
|
|
|
|
|
volumeID,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IsSupportedVolumeCapabilityAccessMode valides the requested access mode
|
|
|
|
|
func IsSupportedVolumeCapabilityAccessMode(
|
|
|
|
|
accessMode csi.VolumeCapability_AccessMode_Mode,
|
|
|
|
|
) bool {
|
|
|
|
|
|
|
|
|
|
for _, access := range SupportedVolumeCapabilityAccessModes {
|
|
|
|
|
if accessMode == access.Mode {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// newControllerCapabilities returns a list
|
|
|
|
|
// of this controller's capabilities
|
|
|
|
|
func newControllerCapabilities() []*csi.ControllerServiceCapability {
|
|
|
|
|
fromType := func(
|
|
|
|
|
cap csi.ControllerServiceCapability_RPC_Type,
|
|
|
|
|
) *csi.ControllerServiceCapability {
|
|
|
|
|
return &csi.ControllerServiceCapability{
|
|
|
|
|
Type: &csi.ControllerServiceCapability_Rpc{
|
|
|
|
|
Rpc: &csi.ControllerServiceCapability_RPC{
|
|
|
|
|
Type: cap,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var capabilities []*csi.ControllerServiceCapability
|
|
|
|
|
for _, cap := range []csi.ControllerServiceCapability_RPC_Type{
|
|
|
|
|
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
|
|
|
|
|
} {
|
|
|
|
|
capabilities = append(capabilities, fromType(cap))
|
|
|
|
|
}
|
|
|
|
|
return capabilities
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// validateRequest validates if the requested service is
|
|
|
|
|
// supported by the driver
|
|
|
|
|
func (cs *controller) validateRequest(
|
|
|
|
|
c csi.ControllerServiceCapability_RPC_Type,
|
|
|
|
|
) error {
|
|
|
|
|
|
|
|
|
|
for _, cap := range cs.capabilities {
|
|
|
|
|
if c == cap.GetRpc().GetType() {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return status.Error(
|
|
|
|
|
codes.InvalidArgument,
|
|
|
|
|
fmt.Sprintf("failed to validate request: {%s} is not supported", c),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cs *controller) validateVolumeCreateReq(req *csi.CreateVolumeRequest) error {
|
|
|
|
|
err := cs.validateRequest(
|
|
|
|
|
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrapf(
|
|
|
|
|
err,
|
|
|
|
|
"failed to handle create volume request for {%s}",
|
|
|
|
|
req.GetName(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if req.GetName() == "" {
|
|
|
|
|
return status.Error(
|
|
|
|
|
codes.InvalidArgument,
|
|
|
|
|
"failed to handle create volume request: missing volume name",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
volCapabilities := req.GetVolumeCapabilities()
|
|
|
|
|
if volCapabilities == nil {
|
|
|
|
|
return status.Error(
|
|
|
|
|
codes.InvalidArgument,
|
|
|
|
|
"failed to handle create volume request: missing volume capabilities",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|