mirror of
https://github.com/oracle/zfssa-csi-driver.git
synced 2025-06-29 23:16:55 +00:00
393 lines
12 KiB
Go
393 lines
12 KiB
Go
/*
|
|
* Copyright (c) 2021, 2024, Oracle and/or its affiliates.
|
|
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
|
|
*/
|
|
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/container-storage-interface/spec/lib/go/csi"
|
|
"github.com/oracle/zfssa-csi-driver/pkg/utils"
|
|
"github.com/oracle/zfssa-csi-driver/pkg/zfssarest"
|
|
context2 "golang.org/x/net/context"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"net/http"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// ZFSSA block volume
|
|
type zLUN struct {
|
|
bolt *utils.Bolt
|
|
refcount int32
|
|
state volumeState
|
|
href string
|
|
id *utils.VolumeId
|
|
capacity int64
|
|
accessModes []csi.VolumeCapability_AccessMode
|
|
source *csi.VolumeContentSource
|
|
initiatorgroup []string
|
|
targetgroup string ``
|
|
}
|
|
|
|
var (
|
|
// access modes supported by block volumes.
|
|
blockVolumeCaps = []csi.VolumeCapability_AccessMode{
|
|
{Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER},
|
|
}
|
|
)
|
|
|
|
// Creates a new LUN structure. If no information is provided (luninfo is nil), this
|
|
// method cannot fail. If information is provided, it will fail if it cannot create
|
|
// a volume ID.
|
|
func newLUN(vid *utils.VolumeId) *zLUN {
|
|
lun := new(zLUN)
|
|
lun.id = vid
|
|
lun.bolt = utils.NewBolt()
|
|
lun.state = stateCreating
|
|
return lun
|
|
}
|
|
|
|
func (lun *zLUN) create(ctx context.Context, token *zfssarest.Token,
|
|
req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
|
|
|
|
utils.GetLogCTRL(ctx, 5).Println("lun.create")
|
|
|
|
capacityRange := req.GetCapacityRange()
|
|
capabilities := req.GetVolumeCapabilities()
|
|
|
|
_, luninfo, httpStatus, err := zfssarest.CreateLUN(ctx, token,
|
|
req.GetName(), getVolumeSize(capacityRange), &req.Parameters)
|
|
if err != nil {
|
|
if httpStatus != http.StatusConflict {
|
|
lun.state = stateDeleted
|
|
return nil, err
|
|
}
|
|
|
|
utils.GetLogCTRL(ctx, 5).Println("LUN already exits")
|
|
// The creation failed because the appliance already has a LUN
|
|
// with the same name. We get the information from the appliance,
|
|
// update the LUN context and check its compatibility with the request.
|
|
if lun.state == stateCreated {
|
|
luninfo, _, err := zfssarest.GetLun(ctx, token,
|
|
req.Parameters["pool"], req.Parameters["project"], req.GetName())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lun.setInfo(luninfo)
|
|
}
|
|
|
|
// The LUN has already been created. The compatibility of the
|
|
// capacity range and accessModes is checked.
|
|
if !compareCapacityRange(capacityRange, lun.capacity) {
|
|
return nil,
|
|
status.Errorf(codes.AlreadyExists,
|
|
"Volume (%s) is already on target (%s),"+
|
|
" capacity range incompatible (%v), requested (%v/%v)",
|
|
lun.id.Name, lun.id.Zfssa, lun.capacity,
|
|
capacityRange.RequiredBytes, capacityRange.LimitBytes)
|
|
}
|
|
if !compareCapabilities(capabilities, lun.accessModes, true) {
|
|
return nil,
|
|
status.Errorf(codes.AlreadyExists,
|
|
"Volume (%s) is already on target (%s), accessModes are incompatible",
|
|
lun.id.Name, lun.id.Zfssa)
|
|
}
|
|
} else {
|
|
lun.setInfo(luninfo)
|
|
}
|
|
|
|
utils.GetLogCTRL(ctx, 5).Printf(
|
|
"LUN created: name=%s, target=%s, assigned_number=%d",
|
|
luninfo.CanonicalName, luninfo.TargetGroup, luninfo.AssignedNumber[0])
|
|
|
|
return &csi.CreateVolumeResponse{
|
|
Volume: &csi.Volume{
|
|
VolumeId: lun.id.String(),
|
|
CapacityBytes: lun.capacity,
|
|
VolumeContext: req.GetParameters()}}, nil
|
|
}
|
|
|
|
func (lun *zLUN) cloneSnapshot(ctx context.Context, token *zfssarest.Token,
|
|
req *csi.CreateVolumeRequest, zsnap *zSnapshot) (*csi.CreateVolumeResponse, error) {
|
|
|
|
utils.GetLogCTRL(ctx, 5).Println("lun.cloneSnapshot")
|
|
|
|
parameters := make(map[string]interface{})
|
|
parameters["project"] = req.Parameters["project"]
|
|
parameters["share"] = req.GetName()
|
|
parameters["initiatorgroup"] = []string{zfssarest.MaskAll}
|
|
|
|
luninfo, _, err := zfssarest.CloneLunSnapshot(ctx, token, zsnap.getHref(), parameters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lun.setInfo(luninfo)
|
|
|
|
return &csi.CreateVolumeResponse{
|
|
Volume: &csi.Volume{
|
|
VolumeId: lun.id.String(),
|
|
CapacityBytes: lun.capacity,
|
|
VolumeContext: req.GetParameters(),
|
|
ContentSource: req.GetVolumeContentSource(),
|
|
}}, nil
|
|
}
|
|
|
|
func (lun *zLUN) delete(ctx context.Context, token *zfssarest.Token) (*csi.DeleteVolumeResponse, error) {
|
|
|
|
utils.GetLogCTRL(ctx, 5).Println("lun.delete")
|
|
|
|
if lun.state == stateCreated {
|
|
_, httpStatus, err := zfssarest.DeleteLun(ctx, token, lun.id.Pool, lun.id.Project, lun.id.Name)
|
|
if err != nil && httpStatus != http.StatusNotFound {
|
|
return nil, err
|
|
}
|
|
|
|
lun.state = stateDeleted
|
|
}
|
|
|
|
return &csi.DeleteVolumeResponse{}, nil
|
|
}
|
|
|
|
func (lun *zLUN) cloneVolume(ctx context.Context, token *zfssarest.Token,
|
|
req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
|
|
|
|
// Create a snapshot to base the clone on
|
|
|
|
// Clone the snapshot to the volume
|
|
|
|
utils.GetLogCTRL(ctx, 5).Println("lun.cloneVolume")
|
|
return nil, status.Error(codes.Unimplemented, "LUN cloneVolume not implemented yet")
|
|
}
|
|
|
|
func (lun *zLUN) controllerPublishVolume(ctx context.Context, token *zfssarest.Token,
|
|
req *csi.ControllerPublishVolumeRequest, nodeName string) (*csi.ControllerPublishVolumeResponse, error) {
|
|
|
|
utils.GetLogCTRL(ctx, 5).Println("lun.controllerPublishVolume")
|
|
|
|
pool := lun.id.Pool
|
|
project := lun.id.Project
|
|
name := lun.id.Name
|
|
|
|
list, err := zfssarest.GetInitiatorGroupList(ctx, token, pool, project, name)
|
|
if err != nil {
|
|
// Log something
|
|
return nil, err
|
|
}
|
|
|
|
// When the driver creates a LUN or clones a Lun from a snapshot of another Lun,
|
|
// it masks the initiator group of the Lun using zfssarest.MaskAll value.
|
|
// When the driver unpublishes the Lun, it also masks the initiator group.
|
|
// This block is to test if the Lun to publish was created or unpublished
|
|
// by the driver. Publishing a Lun with unmasked initiator group fails
|
|
// to avoid mistakenly publishing a Lun that may be in use by other entity.
|
|
utils.GetLogCTRL(ctx, 5).Printf("Volume to publish: %s:%s", lun.id, list[0])
|
|
if len(list) != 1 || list[0] != zfssarest.MaskAll {
|
|
var msg string
|
|
if len(list) > 0 {
|
|
msg = fmt.Sprintf("Volume (%s:%s) may already be published", lun.id, list[0])
|
|
} else {
|
|
msg = fmt.Sprintf("Volume (%s) did not return an initiator group list", lun.id)
|
|
}
|
|
return nil, status.Error(codes.FailedPrecondition, msg)
|
|
}
|
|
|
|
// Reset the masked initiator group with one named by the current node name.
|
|
// There must be initiator groups on ZFSSA defined by the node names.
|
|
_, err = zfssarest.SetInitiatorGroupList(ctx, token, pool, project, name, nodeName)
|
|
if err != nil {
|
|
// Log something
|
|
return nil, err
|
|
}
|
|
|
|
return &csi.ControllerPublishVolumeResponse{}, nil
|
|
}
|
|
|
|
func (lun *zLUN) controllerUnpublishVolume(ctx context.Context, token *zfssarest.Token,
|
|
req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) {
|
|
|
|
utils.GetLogCTRL(ctx, 5).Println("lun.controllerUnpublishVolume")
|
|
|
|
pool := lun.id.Pool
|
|
project := lun.id.Project
|
|
name := lun.id.Name
|
|
|
|
code, err := zfssarest.SetInitiatorGroupList(ctx, token, pool, project, name, zfssarest.MaskAll)
|
|
if err != nil {
|
|
utils.GetLogCTRL(ctx, 5).Println("Could not unpublish volume {}, code {}", lun, code)
|
|
if code != 404 {
|
|
return nil, err
|
|
}
|
|
utils.GetLogCTRL(ctx, 5).Println("Unpublish failed because LUN was deleted, return success")
|
|
}
|
|
|
|
return &csi.ControllerUnpublishVolumeResponse{}, nil
|
|
}
|
|
|
|
func (lun *zLUN) validateVolumeCapabilities(ctx context.Context, token *zfssarest.Token,
|
|
req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
|
|
|
|
utils.GetLogCTRL(ctx, 5).Println("lun.validateVolumeCapabilities")
|
|
|
|
if areBlockVolumeCapsValid(req.VolumeCapabilities) {
|
|
return &csi.ValidateVolumeCapabilitiesResponse{
|
|
Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{
|
|
VolumeCapabilities: req.VolumeCapabilities,
|
|
},
|
|
Message: "",
|
|
}, nil
|
|
} else {
|
|
return &csi.ValidateVolumeCapabilitiesResponse{
|
|
Message: "One or more volume accessModes failed",
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func (lun *zLUN) controllerExpandVolume(ctx context.Context, token *zfssarest.Token,
|
|
req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) {
|
|
return nil, status.Error(codes.OutOfRange, "Not allowed for block devices")
|
|
}
|
|
|
|
func (lun *zLUN) nodeStageVolume(ctx context.Context, token *zfssarest.Token,
|
|
req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (lun *zLUN) nodeUnstageVolume(ctx context.Context, token *zfssarest.Token,
|
|
req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (lun *zLUN) nodePublishVolume(ctx context.Context, token *zfssarest.Token,
|
|
req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (lun *zLUN) nodeUnpublishVolume(ctx context.Context, token *zfssarest.Token,
|
|
req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (lun *zLUN) nodeGetVolumeStats(ctx context.Context, token *zfssarest.Token,
|
|
req *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (lun *zLUN) getDetails(ctx context2.Context, token *zfssarest.Token) (int, error) {
|
|
lunInfo, httpStatus, err := zfssarest.GetLun(ctx, token, lun.id.Pool, lun.id.Project, lun.id.Name)
|
|
if err != nil {
|
|
return httpStatus, err
|
|
}
|
|
lun.setInfo(lunInfo)
|
|
return httpStatus, nil
|
|
}
|
|
|
|
func (lun *zLUN) getSnapshotsList(ctx context.Context, token *zfssarest.Token) (
|
|
[]*csi.ListSnapshotsResponse_Entry, error) {
|
|
|
|
snapList, err := zfssarest.GetSnapshots(ctx, token, lun.href)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return zfssaSnapshotList2csiSnapshotList(ctx, token.Name, snapList), nil
|
|
}
|
|
|
|
func (lun *zLUN) getState() volumeState { return lun.state }
|
|
func (lun *zLUN) getName() string { return lun.id.Name }
|
|
func (lun *zLUN) getHref() string { return lun.href }
|
|
func (lun *zLUN) getVolumeID() *utils.VolumeId { return lun.id }
|
|
func (lun *zLUN) getCapacity() int64 { return lun.capacity }
|
|
func (lun *zLUN) isBlock() bool { return true }
|
|
|
|
func (lun *zLUN) getSnapshots(ctx context.Context, token *zfssarest.Token) ([]zfssarest.Snapshot, error) {
|
|
return zfssarest.GetSnapshots(ctx, token, lun.href)
|
|
}
|
|
|
|
func (lun *zLUN) setInfo(volInfo interface{}) {
|
|
switch luninfo := volInfo.(type) {
|
|
case *zfssarest.Lun:
|
|
lun.capacity = int64(luninfo.VolumeSize)
|
|
lun.href = luninfo.Href
|
|
lun.initiatorgroup = luninfo.InitiatorGroup
|
|
lun.targetgroup = luninfo.TargetGroup
|
|
lun.state = stateCreated
|
|
default:
|
|
panic("lun.setInfo called with wrong type")
|
|
}
|
|
}
|
|
|
|
// Waits until the file system is available and, when it is, returns with its current state.
|
|
func (lun *zLUN) hold(ctx context.Context) volumeState {
|
|
utils.GetLogCTRL(ctx, 5).Printf("holding lun (%s)", lun.id.Name)
|
|
atomic.AddInt32(&lun.refcount, 1)
|
|
return lun.state
|
|
}
|
|
|
|
// Releases the file system and returns its current reference count.
|
|
func (lun *zLUN) release(ctx context.Context) (int32, volumeState) {
|
|
utils.GetLogCTRL(ctx, 5).Printf("releasing lun (%s)", lun.id.Name)
|
|
return atomic.AddInt32(&lun.refcount, -1), lun.state
|
|
}
|
|
|
|
func (lun *zLUN) lock(ctx context.Context) volumeState {
|
|
utils.GetLogCTRL(ctx, 5).Printf("locking %s", lun.id.String())
|
|
lun.bolt.Lock(ctx)
|
|
utils.GetLogCTRL(ctx, 5).Printf("%s is locked", lun.id.String())
|
|
return lun.state
|
|
}
|
|
|
|
func (lun *zLUN) unlock(ctx context.Context) (int32, volumeState) {
|
|
lun.bolt.Unlock(ctx)
|
|
utils.GetLogCTRL(ctx, 5).Printf("%s is unlocked", lun.id.String())
|
|
return lun.refcount, lun.state
|
|
}
|
|
|
|
// Validates the block specific parameters of the create request.
|
|
func validateCreateBlockVolumeReq(ctx context.Context, token *zfssarest.Token, req *csi.CreateVolumeRequest) error {
|
|
|
|
reqCaps := req.GetVolumeCapabilities()
|
|
if !areBlockVolumeCapsValid(reqCaps) {
|
|
return status.Error(codes.InvalidArgument, "invalid volume accessModes")
|
|
}
|
|
|
|
parameters := req.GetParameters()
|
|
tg, ok := parameters["targetGroup"]
|
|
if !ok || len(tg) < 1 {
|
|
return status.Error(codes.InvalidArgument, "a valid ZFSSA target group is required ")
|
|
}
|
|
|
|
_, err := zfssarest.GetTargetGroup(ctx, token, "iscsi", tg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Checks whether the capability list is all supported.
|
|
func areBlockVolumeCapsValid(volCaps []*csi.VolumeCapability) bool {
|
|
|
|
hasSupport := func(cap *csi.VolumeCapability) bool {
|
|
for _, c := range blockVolumeCaps {
|
|
if c.GetMode() == cap.AccessMode.GetMode() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
foundAll := true
|
|
for _, c := range volCaps {
|
|
if !hasSupport(c) {
|
|
foundAll = false
|
|
break
|
|
}
|
|
}
|
|
|
|
return foundAll
|
|
}
|