WIP: Delete Volume when creation fails with status ERROR - #1452
Conversation
Signed-off-by: Niclas Schad <niclas.schad@stackit.cloud>
|
Skipping CI for Draft Pull Request. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
| return nil, status.Error(codes.AlreadyExists, "Volume Already exists with same name and different capacity") | ||
| } | ||
| if *vols[0].Status != stackitclient.VolumeAvailableStatus { | ||
| return nil, status.Error(codes.Internal, fmt.Sprintf("Volume %s is not in available state", *vols[0].Id)) |
There was a problem hiding this comment.
I needs to be also handled here or?
There was a problem hiding this comment.
No, we can't. This would remove volumes that can potentially have data in it. Think about this scenario
1. User creates Volume with data unrelated to SKE or any CSI
2. Volume enters bad state due to reasons
3. User tries to import Volume (which is possible) back into Kubernetes
4. Volume is now managed by CSI and will be deleted because of 2.) instead of being stuck.
There was a problem hiding this comment.
I don't think you can import a volume and land here in the CreateVolume request. The volume name contains the PVC name. Do you see any reason why this function gets called after a volume is created?
There was a problem hiding this comment.
Yeah that was wrong, thats is the ControllerGetVolume RPC. disregard what I said there
Signed-off-by: Niclas Schad <niclas.schad@stackit.cloud>
Signed-off-by: Niclas Schad <niclas.schad@stackit.cloud>
| return nil, status.Error(codes.AlreadyExists, "Volume Already exists with same name and different capacity") | ||
| } | ||
| if *vols[0].Status != stackitclient.VolumeAvailableStatus { | ||
| return nil, status.Error(codes.Internal, fmt.Sprintf("Volume %s is not in available state", *vols[0].Id)) |
There was a problem hiding this comment.
I don't think you can import a volume and land here in the CreateVolume request. The volume name contains the PVC name. Do you see any reason why this function gets called after a volume is created?
| if cs.Driver.deleteVolumesInErrorState { | ||
| cs.deleteVolumeInError(ctx, vol) | ||
| } |
There was a problem hiding this comment.
We also could say we just error here and only delete on the other place above.
There was a problem hiding this comment.
Yes that would be an option. I like it
Signed-off-by: Niclas Schad <niclas.schad@stackit.cloud>
stackit-ske-bot
left a comment
There was a problem hiding this comment.
SKE Code Review
Architectural Feedback
-
Simplify
WaitVolumeTargetStatusWithCustomBackoffSignature:
In pkg/stackit/client/iaas.go#L52,WaitVolumeTargetStatusWithCustomBackoffwas changed from acceptingvolumeID stringtovol **iaas.Volumeto allow mutating the pointer in-place for cleanup after creation. However, since the subsequent cleanup call afterWaitVolumeTargetStatusWithCustomBackoffwas removed in commit 87917aa, mutatingvolin-place is no longer needed in pkg/csi/blockstorage/controllerserver.go#L271.Passing a double pointer (
**iaas.Volume) across interface boundaries is unidiomatic in Go, introduces nil dereference risks (e.g.(*vol).GetId()), and forced modifications across multiple unit tests in pkg/csi/blockstorage/controllerserver_test.go and pkg/stackit/client/mock/iaas_mock.go. Reverting the interface method back tovolumeID stringsimplifies the API, avoids unnecessary mock changes, and keeps it consistent withWaitVolumeTargetStatus.
Findings & Feedback
- All findings and concrete recommendations have been provided as inline code suggestions above.
Verdict
Comment — The flag and cleanup logic for existing error-state volumes in CreateVolume are a great improvement. Please address the nil-safety items and consider reverting the double-pointer signature in WaitVolumeTargetStatusWithCustomBackoff.
| @@ -137,6 +137,9 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol | |||
| return nil, status.Error(codes.AlreadyExists, "Volume Already exists with same name and different capacity") | |||
| } | |||
| if *vols[0].Status != stackitclient.VolumeAvailableStatus { | |||
There was a problem hiding this comment.
| if *vols[0].Status != stackitclient.VolumeAvailableStatus { | |
| if vols[0].GetStatus() != stackitclient.VolumeAvailableStatus { | |
| if cs.Driver.deleteVolumesInErrorState { | |
| cs.deleteVolumeInError(ctx, &vols[0]) | |
| } | |
| return nil, status.Errorf(codes.Internal, "Volume %s is not in available state", vols[0].GetId()) | |
| } |
Rationale: Use getter methods GetStatus() and GetId() (or status.Errorf) to safely prevent potential nil pointer dereferences when inspecting vols[0].
| if err != nil { | ||
| klog.Errorf("Failed to WaitVolumeTargetStatus of volume %s: %v", *vol.Id, err) | ||
| klog.Errorf("Failed to WaitVolumeTargetStatus of volume %s: %v", vol.GetId(), err) | ||
| return nil, status.Error(codes.Internal, fmt.Sprintf("CreateVolume Volume %s failed getting available in time: %v", *vol.Id, err)) |
There was a problem hiding this comment.
| return nil, status.Error(codes.Internal, fmt.Sprintf("CreateVolume Volume %s failed getting available in time: %v", *vol.Id, err)) | |
| return nil, status.Errorf(codes.Internal, "CreateVolume Volume %s failed getting available in time: %v", vol.GetId(), err) |
Rationale: Use vol.GetId() rather than direct pointer dereference *vol.Id to maintain nil-safety and consistency with line 278.
| } | ||
|
|
||
| func (cs *controllerServer) deleteVolumeInError(ctx context.Context, vol *iaas.Volume) { | ||
| cloud := cs.Instance |
There was a problem hiding this comment.
| cloud := cs.Instance | |
| func (cs *controllerServer) deleteVolumeInError(ctx context.Context, vol *iaas.Volume) { | |
| if vol == nil { | |
| return | |
| } | |
| cloud := cs.Instance |
Rationale: Add a defensive nil check on vol before accessing vol.GetStatus() and vol.GetId().
|
|
||
| func (i *iaasClient) WaitVolumeTargetStatusWithCustomBackoff(ctx context.Context, volumeID string, tStatus []string, backoff *wait.Backoff) error { | ||
| func (i *iaasClient) WaitVolumeTargetStatusWithCustomBackoff(ctx context.Context, vol **iaas.Volume, tStatus []string, backoff *wait.Backoff) error { | ||
| volID := (*vol).GetId() |
There was a problem hiding this comment.
| volID := (*vol).GetId() | |
| func (i *iaasClient) WaitVolumeTargetStatusWithCustomBackoff(ctx context.Context, vol **iaas.Volume, tStatus []string, backoff *wait.Backoff) error { | |
| if vol == nil || *vol == nil { | |
| return fmt.Errorf("volume pointer cannot be nil") | |
| } | |
| volID := (*vol).GetId() |
Rationale: Guard against nil pointer dereference if vol or *vol is nil, or consider reverting the method signature to accept volumeID string as noted in the architectural feedback.
Signed-off-by: Felix Breuer <f.breuer94@gmail.com>
How to categorize this PR?
/kind enhancement
What this PR does / why we need it:
The idea is to automatically delete the volume when status is
ERROR. We do this there is no left-over volumes even though the CreateVolume RPC failed.Which issue(s) this PR fixes:
Fixes #
Special notes for your reviewer:
Breaking changes: