Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions pkg/csi/manila/controllerserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS
// Retrieve the source share

if sourceShare, res.err = manilaClient.GetShareByID(req.GetSourceVolumeId()); res.err != nil {
if isManilaErrNotFound(res.err) {
return nil, status.Errorf(codes.NotFound, "failed to create a snapshot (%s) for share %s because the share doesn't exist: %v", req.GetName(), req.GetSourceVolumeId(), err)
}

return nil, status.Errorf(codes.Internal, "failed to retrieve source share %s when creating a snapshot (%s): %v", req.GetSourceVolumeId(), req.GetName(), res.err)
}

Expand All @@ -246,10 +250,14 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS
return nil, status.Errorf(codes.DeadlineExceeded, "deadline exceeded while waiting for snapshot %s of share %s to become available", snapshot.ID, req.GetSourceVolumeId())
}

if isManilaErrNotFound(res.err) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

return nil, status.Errorf(codes.NotFound, "failed to create a snapshot (%s) for share %s because the share doesn't exist: %v", req.GetName(), req.GetSourceVolumeId(), err)
}

return nil, status.Errorf(codes.Internal, "failed to create a snapshot (%s) of share %s: %v", req.GetName(), req.GetSourceVolumeId(), res.err)
}

if res.err = verifySnapshotCompatibility(sourceShare, snapshot, req); res.err != nil {
if res.err = verifySnapshotCompatibility(snapshot, req); res.err != nil {
return nil, status.Errorf(codes.AlreadyExists, "a snapshot named %s already exists, but is incompatible with the request: %v", req.GetName(), res.err)
}

Expand Down Expand Up @@ -332,7 +340,62 @@ func (cs *controllerServer) ControllerGetCapabilities(ctx context.Context, req *
}

func (cs *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
return nil, status.Error(codes.Unimplemented, "")
if err := validateValidateVolumeCapabilitiesRequest(req); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

osOpts, err := options.NewOpenstackOptions(req.GetSecrets())
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid OpenStack secrets: %v", err)
}

for _, volCap := range req.GetVolumeCapabilities() {
if volCap.GetBlock() != nil {
return &csi.ValidateVolumeCapabilitiesResponse{Message: "block access type is not allowed"}, nil
}

if volCap.GetMount() == nil {
return &csi.ValidateVolumeCapabilitiesResponse{Message: "volume must be accessible via filesystem API"}, nil
}

if volCap.GetAccessMode().GetMode() == csi.VolumeCapability_AccessMode_UNKNOWN {
return &csi.ValidateVolumeCapabilitiesResponse{Message: "unknown volume access mode"}, nil
}
}

manilaClient, err := cs.d.manilaClientBuilder.New(osOpts)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "failed to create Manila v2 client: %v", err)
}

share, err := manilaClient.GetShareByID(req.GetVolumeId())
if err != nil {
if isManilaErrNotFound(err) {
return nil, status.Errorf(codes.NotFound, "share %s not found: %v", req.GetVolumeId(), err)
}

return nil, status.Errorf(codes.Internal, "failed to retrieve share %s: %v", req.GetVolumeId(), err)
}

if share.Status != shareAvailable {
if share.Status == shareCreating {
return nil, status.Errorf(codes.Unavailable, "share %s is in transient creating state", share.ID)
}

return nil, status.Errorf(codes.FailedPrecondition, "share %s is in an unexpected state: wanted %s, got %s", share.ID, shareAvailable, share.Status)
}

if !compareProtocol(share.ShareProto, cs.d.shareProto) {
return nil, status.Errorf(codes.InvalidArgument, "share protocol mismatch: wanted %s, got %s", cs.d.shareProto, share.ShareProto)
}

return &csi.ValidateVolumeCapabilitiesResponse{
Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{
VolumeContext: req.GetVolumeContext(),
VolumeCapabilities: req.GetVolumeCapabilities(),
Parameters: req.GetParameters(),
},
}, nil
}

func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {
Expand Down
14 changes: 9 additions & 5 deletions pkg/csi/manila/nodeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ func (ns *nodeServer) buildVolumeContext(volID volumeID, shareOpts *options.Node
}

if share.Status != shareAvailable {
return nil, nil, status.Errorf(codes.InvalidArgument, "invalid share status for volume %s (share ID %s): expected 'available', got '%s'",
if share.Status == shareCreating {
return nil, nil, status.Errorf(codes.Unavailable, "share %s for volume %s is in transient creating state", share.ID, volID)
}

return nil, nil, status.Errorf(codes.FailedPrecondition, "invalid share status for volume %s (share ID %s): expected 'available', got '%s'",
volID, share.ID, share.Status)
}

Expand Down Expand Up @@ -156,12 +160,12 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis

shareOpts, err := options.NewNodeVolumeContext(req.GetVolumeContext())
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid volume parameters: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "invalid volume context: %v", err)
}

osOpts, err := options.NewOpenstackOptions(req.GetSecrets())
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid volume secret: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "invalid OpenStack secrets: %v", err)
}

volID := volumeID(req.GetVolumeId())
Expand Down Expand Up @@ -240,12 +244,12 @@ func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol

shareOpts, err := options.NewNodeVolumeContext(req.GetVolumeContext())
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid volume parameters: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "invalid volume context: %v", err)
}

osOpts, err := options.NewOpenstackOptions(req.GetSecrets())
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid volume secret: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "invalid OpenStack secrets: %v", err)
}

volID := volumeID(req.GetVolumeId())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to check if specified volume exists or not here also?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...which returns INVALID_ARGUMENT if it fails to retrieve the share - this is of course not correct. I'll fix this as well

Expand Down
7 changes: 3 additions & 4 deletions pkg/csi/manila/share.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"fmt"
"time"

"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/cloud-provider-openstack/pkg/csi/manila/manilaclient"
Expand Down Expand Up @@ -51,7 +50,7 @@ func getOrCreateShare(shareName string, createOpts *shares.CreateOpts, manilaCli
// First, check if the share already exists or needs to be created

if share, err = manilaClient.GetShareByName(shareName); err != nil {
if _, ok := err.(gophercloud.ErrResourceNotFound); ok {
if isManilaErrNotFound(err) {
// It doesn't exist, create it

var createErr error
Expand All @@ -77,7 +76,7 @@ func getOrCreateShare(shareName string, createOpts *shares.CreateOpts, manilaCli

func deleteShare(shareID string, manilaClient manilaclient.Interface) error {
if err := manilaClient.DeleteShare(shareID); err != nil {
if _, ok := err.(gophercloud.ErrResourceNotFound); ok {
if isManilaErrNotFound(err) {
klog.V(4).Infof("share %s not found, assuming it to be already deleted", shareID)
} else {
return err
Expand Down Expand Up @@ -121,7 +120,7 @@ func waitForShareStatus(shareID, currentStatus, desiredStatus string, successOnN
share, err = manilaClient.GetShareByID(shareID)

if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok && successOnNotFound {
if isManilaErrNotFound(err) && successOnNotFound {
return true, nil
}

Expand Down
10 changes: 5 additions & 5 deletions pkg/csi/manila/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ package manila

import (
"fmt"
"github.com/gophercloud/gophercloud"
"time"

"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/cloud-provider-openstack/pkg/csi/manila/manilaclient"
"k8s.io/klog"
"time"
)

const (
Expand All @@ -47,7 +47,7 @@ func getOrCreateSnapshot(snapName, sourceShareID string, manilaClient manilaclie
// First, check if the snapshot already exists or needs to be created

if snapshot, err = manilaClient.GetSnapshotByName(snapName); err != nil {
if _, ok := err.(gophercloud.ErrResourceNotFound); ok {
if isManilaErrNotFound(err) {
// It doesn't exist, create it

opts := snapshots.CreateOpts{
Expand All @@ -74,7 +74,7 @@ func getOrCreateSnapshot(snapName, sourceShareID string, manilaClient manilaclie

func deleteSnapshot(snapID string, manilaClient manilaclient.Interface) error {
if err := manilaClient.DeleteSnapshot(snapID); err != nil {
if _, ok := err.(gophercloud.ErrResourceNotFound); ok {
if isManilaErrNotFound(err) {
klog.V(4).Infof("snapshot %s not found, assuming it to be already deleted", snapID)
} else {
return err
Expand Down Expand Up @@ -118,7 +118,7 @@ func waitForSnapshotStatus(snapshotID, currentStatus, desiredStatus string, succ
snapshot, err = manilaClient.GetSnapshotByID(snapshotID)

if err != nil {
if _, ok := err.(gophercloud.ErrResourceNotFound); ok && successOnNotFound {
if isManilaErrNotFound(err) && successOnNotFound {
return true, nil
}

Expand Down
59 changes: 52 additions & 7 deletions pkg/csi/manila/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"strings"

"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/messages"
"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares"
"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots"
Expand Down Expand Up @@ -176,6 +177,26 @@ func lastResourceError(resourceID string, manilaClient manilaclient.Interface) (
return manilaErrorMessage{message: "unknown error"}, nil
}

func isManilaErrNotFound(err error) bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have IsNotFound error defined here , you can reuse it the same https://github.com/kubernetes/cloud-provider-openstack/blob/master/pkg/util/errors/errors.go#L25

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out! Will update

if err == nil {
return false
}

if _, isDefault404 := err.(gophercloud.ErrDefault404); isDefault404 {
return true
}

if _, isResourceNotFound := err.(gophercloud.ErrResourceNotFound); isResourceNotFound {
return true
}

return false
}

func compareProtocol(protoA, protoB string) bool {
return strings.ToUpper(protoA) == strings.ToUpper(protoB)
}

//
// Controller service request validation
//
Expand All @@ -192,7 +213,7 @@ func validateCreateVolumeRequest(req *csi.CreateVolumeRequest) error {

for _, cap := range reqCaps {
if cap.GetBlock() != nil {
return errors.New("block volume not supported")
return errors.New("block access type not allowed")
}
}

Expand Down Expand Up @@ -257,11 +278,11 @@ func verifyVolumeCompatibility(sizeInGiB int, req *csi.CreateVolumeRequest, shar
}

if share.Size != sizeInGiB {
return fmt.Errorf("size mismatch: wanted %d, got %d", sizeInGiB, share.Size)
return fmt.Errorf("size mismatch: wanted %d, got %d", share.Size, sizeInGiB)
}

if share.ShareProto != shareOpts.Protocol {
return fmt.Errorf("share protocol mismatch: wanted %s, got %s", coalesceValue(shareOpts.Protocol), coalesceValue(share.ShareProto))
return fmt.Errorf("share protocol mismatch: wanted %s, got %s", coalesceValue(share.ShareProto), coalesceValue(shareOpts.Protocol))
}

// FIXME shareOpts.Type may be either type name or type ID
Expand All @@ -272,7 +293,7 @@ func verifyVolumeCompatibility(sizeInGiB int, req *csi.CreateVolumeRequest, shar
*/

if share.ShareNetworkID != shareOpts.ShareNetworkID {
return fmt.Errorf("share network ID mismatch: wanted %s, got %s", coalesceValue(shareOpts.ShareNetworkID), coalesceValue(share.ShareNetworkID))
return fmt.Errorf("share network ID mismatch: wanted %s, got %s", coalesceValue(share.ShareNetworkID), coalesceValue(shareOpts.ShareNetworkID))
}

var reqSrcSnapID string
Expand All @@ -287,9 +308,25 @@ func verifyVolumeCompatibility(sizeInGiB int, req *csi.CreateVolumeRequest, shar
return nil
}

func verifySnapshotCompatibility(sourceShare *shares.Share, snapshot *snapshots.Snapshot, req *csi.CreateSnapshotRequest) error {
if sourceShare.ID != req.SourceVolumeId {
return fmt.Errorf("source share ID mismatch: wanted %s, got %s", req.SourceVolumeId, sourceShare.ID)
func verifySnapshotCompatibility(snapshot *snapshots.Snapshot, req *csi.CreateSnapshotRequest) error {
if snapshot.ShareID != req.GetSourceVolumeId() {
return fmt.Errorf("source share ID mismatch: wanted %s, got %s", snapshot.ID, req.GetSourceVolumeId())
}

return nil
}

func validateValidateVolumeCapabilitiesRequest(req *csi.ValidateVolumeCapabilitiesRequest) error {
if req.GetVolumeId() == "" {
return errors.New("volume ID missing in request")
}

if req.GetVolumeCapabilities() == nil || len(req.GetVolumeCapabilities()) == 0 {
return errors.New("volume capabilities cannot be nil or empty")
}

if req.GetSecrets() == nil || len(req.GetSecrets()) == 0 {
return errors.New("stage secrets cannot be nil or empty")
}

return nil
Expand Down Expand Up @@ -320,6 +357,10 @@ func validateNodeStageVolumeRequest(req *csi.NodeStageVolumeRequest) error {
}

func validateNodeUnstageVolumeRequest(req *csi.NodeUnstageVolumeRequest) error {
if req.GetStagingTargetPath() == "" {
return errors.New("staging path missing in request")
}

if req.GetVolumeId() == "" {
return errors.New("volume ID missing in request")
}
Expand Down Expand Up @@ -348,6 +389,10 @@ func validateNodePublishVolumeRequest(req *csi.NodePublishVolumeRequest) error {
}

func validateNodeUnpublishVolumeRequest(req *csi.NodeUnpublishVolumeRequest) error {
if req.GetTargetPath() == "" {
return errors.New("target path missing in request")
}

if req.GetVolumeId() == "" {
return errors.New("volume ID missing in request")
}
Expand Down
11 changes: 9 additions & 2 deletions pkg/csi/manila/volumesource.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package manila

import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand Down Expand Up @@ -81,13 +80,21 @@ func (volumeFromSnapshot) create(req *csi.CreateVolumeRequest, shareName string,

snapshot, err := manilaClient.GetSnapshotByID(snapshotSource.GetSnapshotId())
if err != nil {
if _, ok := err.(gophercloud.ErrResourceNotFound); ok {
if isManilaErrNotFound(err) {
return nil, status.Errorf(codes.NotFound, "source snapshot %s not found: %v", snapshotSource.GetSnapshotId(), err)
}

return nil, status.Errorf(codes.Internal, "failed to retrieve snapshot %s: %v", snapshotSource.GetSnapshotId(), err)
}

if snapshot.Status != snapshotAvailable {
if snapshot.Status == snapshotCreating {
return nil, status.Errorf(codes.Unavailable, "snapshot %s is in transient creating state", snapshot.ID)
}

return nil, status.Errorf(codes.FailedPrecondition, "snapshot %s is in invalid state: expected 'available', got '%s'", snapshot.ID, snapshot.Status)
}

createOpts := &shares.CreateOpts{
SnapshotID: snapshot.ID,
ShareProto: shareOpts.Protocol,
Expand Down