-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simplify attach process #322
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -10,8 +10,6 @@ import ( | |||
|
||||
"github.com/container-storage-interface/spec/lib/go/csi" | ||||
"github.com/linode/linodego" | ||||
"google.golang.org/grpc/codes" | ||||
"google.golang.org/grpc/status" | ||||
|
||||
linodevolumes "github.com/linode/linode-blockstorage-csi-driver/pkg/linode-volumes" | ||||
"github.com/linode/linode-blockstorage-csi-driver/pkg/logger" | ||||
|
@@ -95,31 +93,6 @@ type VolumeParams struct { | |||
Region string | ||||
} | ||||
|
||||
// canAttach indicates whether or not another volume can be attached to the | ||||
// Linode with the given ID. | ||||
// | ||||
// Whether or not another volume can be attached is based on how many instance | ||||
// disks and block storage volumes are currently attached to the instance. | ||||
func (cs *ControllerServer) canAttach(ctx context.Context, instance *linodego.Instance) (canAttach bool, err error) { | ||||
log := logger.GetLogger(ctx) | ||||
log.V(4).Info("Checking if volume can be attached", "instance_id", instance.ID) | ||||
|
||||
// Get the maximum number of volume attachments allowed for the instance | ||||
limit, err := cs.maxAllowedVolumeAttachments(ctx, instance) | ||||
if err != nil { | ||||
return false, err | ||||
} | ||||
|
||||
// List the volumes currently attached to the instance | ||||
volumes, err := cs.client.ListInstanceVolumes(ctx, instance.ID, nil) | ||||
if err != nil { | ||||
return false, errInternal("list instance volumes: %v", err) | ||||
} | ||||
|
||||
// Return true if the number of attached volumes is less than the limit | ||||
return len(volumes) < limit, nil | ||||
} | ||||
|
||||
// maxAllowedVolumeAttachments calculates the maximum number of volumes that can be attached to a Linode instance, | ||||
// taking into account the instance's memory and currently attached disks. | ||||
func (cs *ControllerServer) maxAllowedVolumeAttachments(ctx context.Context, instance *linodego.Instance) (int, error) { | ||||
|
@@ -677,33 +650,6 @@ func (cs *ControllerServer) getInstance(ctx context.Context, linodeID int) (*lin | |||
return instance, nil | ||||
} | ||||
|
||||
// checkAttachmentCapacity checks if the specified instance can accommodate | ||||
// additional volume attachments. It retrieves the maximum number of allowed | ||||
// attachments and compares it with the currently attached volumes. If the | ||||
// limit is exceeded, it returns an error indicating the maximum volume | ||||
// attachments allowed. | ||||
func (cs *ControllerServer) checkAttachmentCapacity(ctx context.Context, instance *linodego.Instance) error { | ||||
log := logger.GetLogger(ctx) | ||||
log.V(4).Info("Entering checkAttachmentCapacity()", "linodeID", instance.ID) | ||||
defer log.V(4).Info("Exiting checkAttachmentCapacity()") | ||||
|
||||
canAttach, err := cs.canAttach(ctx, instance) | ||||
if err != nil { | ||||
return err | ||||
} | ||||
if !canAttach { | ||||
// If the instance cannot accommodate more attachments, retrieve the maximum allowed attachments. | ||||
limit, err := cs.maxAllowedVolumeAttachments(ctx, instance) | ||||
if errors.Is(err, errNilInstance) { | ||||
return errInternal("cannot calculate max volume attachments for a nil instance") | ||||
} else if err != nil { | ||||
return errMaxAttachments // Return an error indicating the maximum attachments limit has been reached. | ||||
} | ||||
return errMaxVolumeAttachments(limit) // Return an error indicating the maximum volume attachments allowed. | ||||
} | ||||
return nil // Return nil if the instance can accommodate more attachments. | ||||
} | ||||
|
||||
// attachVolume attaches the specified volume to the given Linode instance. | ||||
// It logs the action and handles any errors that may occur during the | ||||
// attachment process. If the volume is already attached, it allows for a | ||||
|
@@ -719,13 +665,17 @@ func (cs *ControllerServer) attachVolume(ctx context.Context, volumeID, linodeID | |||
PersistAcrossBoots: &persist, | ||||
}) | ||||
if err != nil { | ||||
code := codes.Internal // Default error code is Internal. | ||||
// Check if the error indicates that the volume is already attached. | ||||
// https://github.com/container-storage-interface/spec/blob/master/spec.md#controllerpublishvolume-errors | ||||
var apiErr *linodego.Error | ||||
if errors.As(err, &apiErr) && strings.Contains(apiErr.Message, "is already attached") { | ||||
code = codes.Unavailable // Allow a retry if the volume is already attached: race condition can occur here | ||||
if errors.As(err, &apiErr) { | ||||
switch { | ||||
case strings.Contains(apiErr.Message, "is already attached"): | ||||
return errAlreadyAttached | ||||
Comment on lines
+672
to
+673
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: We only get this err when the volume is attached to another linode? I think pass the specific err message from the api which could include linode id of another node. This would be helpful in debugging issue in future. |
||||
case strings.Contains(apiErr.Message, "Maximum number of block storage volumes are attached to this Linode"): | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, but this seems brittle when the API changes their text message. How can we check this error, but not rely on the spelling of an API text error message? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's an excellent question. But we have to notice this logic was already done before in CSI :/
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah def not the ideal way to do it. Hopefully linodego can provide better error codes in future so we don't have to build logic based on the err strings :/ |
||||
return errMaxAttachments | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to my suggestion from other comment. We should try to pass along the err message by the api here too |
||||
} | ||||
} | ||||
return status.Errorf(code, "attach volume: %v", err) | ||||
return errInternal("attach volume: %v", err) | ||||
} | ||||
return nil // Return nil if the volume is successfully attached. | ||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,10 @@ var ( | |
// attachments allowed for the instance, call errMaxVolumeAttachments. | ||
errMaxAttachments = status.Error(codes.ResourceExhausted, "max number of volumes already attached to instance") | ||
|
||
// errAlreadyAttached is used to indicate that a volume is already attached | ||
// to a Linode instance. | ||
errAlreadyAttached = status.Error(codes.FailedPrecondition, "volume is already attached") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before merging, I want to wait for an answer on this issue |
||
|
||
// errResizeDown indicates a request would result in a volume being resized | ||
// to be smaller than it currently is. | ||
// | ||
|
@@ -61,10 +65,6 @@ func errRegionMismatch(gotRegion, wantRegion string) error { | |
return status.Errorf(codes.InvalidArgument, "source volume is in region %q, needs to be in region %q", gotRegion, wantRegion) | ||
} | ||
|
||
func errMaxVolumeAttachments(numAttachments int) error { | ||
return status.Errorf(codes.ResourceExhausted, "max number of volumes (%d) already attached to instance", numAttachments) | ||
} | ||
|
||
func errInstanceNotFound(linodeID int) error { | ||
return status.Errorf(codes.NotFound, "linode instance %d not found", linodeID) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there really no value in having these checks client side at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tend to prefer to rely on API internal logic and validation and for CSI to act as a simple pass-through.
The problem I see could be a rate-limiting issue on API (GET vs. POST).
But attacher already has a backoff on error
https://github.com/kubernetes-csi/external-attacher?tab=readme-ov-file#csi-error-and-timeout-handling
But that may be questionable, or we may have to wait for linodego to implement rate-limiting.
That's why this PR was in draft state at first.