From 20ea3a332db7e8d0622dd26ec2ebd99210339ed3 Mon Sep 17 00:00:00 2001 From: Niclas Schad Date: Thu, 16 Jul 2026 13:58:22 +0200 Subject: [PATCH 1/3] reduce API calls by retrieving the AZ from the snapshot directly Signed-off-by: Niclas Schad --- pkg/csi/blockstorage/controllerserver.go | 9 ++------ pkg/csi/blockstorage/controllerserver_test.go | 21 +++++++------------ 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/pkg/csi/blockstorage/controllerserver.go b/pkg/csi/blockstorage/controllerserver.go index 02f8c1fb..044e3c5e 100644 --- a/pkg/csi/blockstorage/controllerserver.go +++ b/pkg/csi/blockstorage/controllerserver.go @@ -179,13 +179,8 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } // Only continue checking if the Snapshot is found if !stackiterrors.IsNotFound(err) { - // TODO: Remove cloud.GetVolume() once IaaS adds the AZ field in the response of GetSnapshotByID() - snapshotVolSrc, err := cloud.GetVolume(ctx, snap.GetVolumeId()) - if err != nil { - return nil, status.Errorf(codes.Internal, "Failed to retrieve the source volume of snapshot %s: %v", sourceSnapshotID, err) - } - if snapshotVolSrc.AvailabilityZone != volAvailability { - return nil, status.Errorf(codes.ResourceExhausted, "Volume must be in the same availability zone as source Snapshot. Got %s Required: %s", volAvailability, snapshotVolSrc.AvailabilityZone) + if snap.GetAvailabilityZone() != volAvailability { + return nil, status.Errorf(codes.ResourceExhausted, "Volume must be in the same availability zone as source Snapshot. Got %s Required: %s", volAvailability, snap.GetAvailabilityZone()) } } diff --git a/pkg/csi/blockstorage/controllerserver_test.go b/pkg/csi/blockstorage/controllerserver_test.go index f20f1d7a..2922364e 100644 --- a/pkg/csi/blockstorage/controllerserver_test.go +++ b/pkg/csi/blockstorage/controllerserver_test.go @@ -298,13 +298,10 @@ var _ = Describe("ControllerServer test", Ordered, func() { } iaasClient.EXPECT().GetSnapshot(gomock.Any(), "snapshot-id").Return(&iaas.Snapshot{ - Id: new("snapshot-id"), - Status: new("AVAILABLE"), - VolumeId: "snapshot-volume-id", - }, nil) - iaasClient.EXPECT().GetVolume(gomock.Any(), "snapshot-volume-id").Return(&iaas.Volume{ - Id: new("snapshot-volume-id"), - AvailabilityZone: "eu01", + Id: new("snapshot-id"), + Status: new("AVAILABLE"), + VolumeId: "snapshot-volume-id", + AvailabilityZone: new("eu01"), }, nil) iaasClient.EXPECT(). CreateVolume(gomock.Any(), gomock.Any()). @@ -420,14 +417,10 @@ var _ = Describe("ControllerServer test", Ordered, func() { } iaasClient.EXPECT().GetSnapshot(gomock.Any(), "snapshot-id").Return(&iaas.Snapshot{ - Id: new("snapshot-id"), - VolumeId: "volume-id", - Status: new("AVAILABLE"), - }, nil) - iaasClient.EXPECT().GetVolume(gomock.Any(), "volume-id").Return(&iaas.Volume{ + Id: new("snapshot-id"), + VolumeId: "volume-id", Status: new("AVAILABLE"), - AvailabilityZone: "eu01", - Id: new("volume-id"), + AvailabilityZone: new("eu01"), }, nil) _, err := fakeCs.CreateVolume(context.Background(), req) From 8c7bae749562f30b6132740ed246233d0775fddb Mon Sep 17 00:00:00 2001 From: Niclas Schad Date: Thu, 16 Jul 2026 13:58:56 +0200 Subject: [PATCH 2/3] add missing volume error codes to volumeErrorStates Signed-off-by: Niclas Schad --- pkg/stackit/client/iaas.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/stackit/client/iaas.go b/pkg/stackit/client/iaas.go index 3195c4ff..56ed1588 100644 --- a/pkg/stackit/client/iaas.go +++ b/pkg/stackit/client/iaas.go @@ -94,7 +94,7 @@ const ( BackupSource VolumeSourceTypes = "backup" ) -var volumeErrorStates = [...]string{"ERROR", "ERROR_RESIZING", "ERROR_DELETING"} +var volumeErrorStates = [...]string{"ERROR", "ERROR_BACKING-UP", "ERROR_DELETING", "ERROR_RESIZING", "ERROR_RESTORING-BACKUP", "ERROR_KMS-ENCRYPTION-PARAMS"} func NewIaaSClient(region, projectID string, options []sdkconfig.ConfigurationOption) (IaaSClient, error) { apiClient, err := iaas.NewAPIClient(options...) From c691c1122da4723c31308a99d59d0fab9fa4a36a Mon Sep 17 00:00:00 2001 From: Niclas Schad Date: Thu, 16 Jul 2026 22:14:03 +0200 Subject: [PATCH 3/3] check vol status before trying to attach; abort early if status is not happy Signed-off-by: Niclas Schad --- pkg/csi/blockstorage/controllerserver.go | 11 ++++++++++- pkg/csi/blockstorage/controllerserver_test.go | 4 ++-- pkg/csi/blockstorage/sanity_test.go | 14 +++++++++++--- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/pkg/csi/blockstorage/controllerserver.go b/pkg/csi/blockstorage/controllerserver.go index 044e3c5e..ad90e57f 100644 --- a/pkg/csi/blockstorage/controllerserver.go +++ b/pkg/csi/blockstorage/controllerserver.go @@ -356,7 +356,7 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs return nil, status.Error(codes.InvalidArgument, "[ControllerPublishVolume] Volume capability must be provided") } - _, err := cloud.GetVolume(ctx, volumeID) + vol, err := cloud.GetVolume(ctx, volumeID) if err != nil { if stackiterrors.IsNotFound(err) { return nil, status.Errorf(codes.NotFound, "[ControllerPublishVolume] Volume %s not found", volumeID) @@ -372,6 +372,15 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs return nil, status.Errorf(codes.Internal, "[ControllerPublishVolume] GetInstanceByID failed with error %v", err) } + // If Volume is already mounted to target instanceID, return OK + if vol.ServerId != nil && *vol.ServerId == instanceID { + return &csi.ControllerPublishVolumeResponse{}, nil + } + + if vol.GetStatus() != stackitclient.VolumeAvailableStatus { + return nil, status.Errorf(codes.Internal, "[ControllerPublishVolume] Volume %s is not in an READY state. Got:%s Want:%s", volumeID, vol.GetStatus(), stackitclient.VolumeAvailableStatus) + } + payload := iaas.AddVolumeToServerPayload{ DeleteOnTermination: new(false), } diff --git a/pkg/csi/blockstorage/controllerserver_test.go b/pkg/csi/blockstorage/controllerserver_test.go index 2922364e..853efe3d 100644 --- a/pkg/csi/blockstorage/controllerserver_test.go +++ b/pkg/csi/blockstorage/controllerserver_test.go @@ -660,7 +660,7 @@ var _ = Describe("ControllerServer test", Ordered, func() { NodeId: "fake", VolumeCapability: stdVolCap, } - iaasClient.EXPECT().GetVolume(gomock.Any(), req.VolumeId).Return(&iaas.Volume{}, nil) + iaasClient.EXPECT().GetVolume(gomock.Any(), req.VolumeId).Return(&iaas.Volume{Status: new("AVAILABLE")}, nil) iaasClient.EXPECT().GetServer(gomock.Any(), "fake").Return(&iaas.Server{}, nil) iaasClient.EXPECT().AttachVolume(gomock.Any(), req.NodeId, req.VolumeId, gomock.Any()).Return(req.VolumeId, nil) iaasClient.EXPECT().WaitDiskAttached(gomock.Any(), req.NodeId, req.VolumeId).Return(nil) @@ -674,7 +674,7 @@ var _ = Describe("ControllerServer test", Ordered, func() { NodeId: "fake", VolumeCapability: stdVolCap, } - iaasClient.EXPECT().GetVolume(gomock.Any(), req.VolumeId).Return(&iaas.Volume{}, nil) + iaasClient.EXPECT().GetVolume(gomock.Any(), req.VolumeId).Return(&iaas.Volume{Status: new("AVAILABLE")}, nil) iaasClient.EXPECT().GetServer(gomock.Any(), req.NodeId).Return(&iaas.Server{}, nil) iaasClient.EXPECT().AttachVolume(gomock.Any(), req.NodeId, req.VolumeId, gomock.Any()).Return("", &oapierror.GenericOpenAPIError{ StatusCode: http.StatusForbidden, diff --git a/pkg/csi/blockstorage/sanity_test.go b/pkg/csi/blockstorage/sanity_test.go index bac6c348..4b251ec6 100644 --- a/pkg/csi/blockstorage/sanity_test.go +++ b/pkg/csi/blockstorage/sanity_test.go @@ -263,7 +263,7 @@ var _ = Describe("CSI sanity test", Ordered, func() { newBackup := &iaas.Backup{ Id: new(uuid.New().String()), Name: new(name), - Status: new("available"), + Status: new("AVAILABLE"), VolumeId: new(volID), SnapshotId: new(snapshotID), CreatedAt: new(time.Now()), @@ -329,7 +329,7 @@ var _ = Describe("CSI sanity test", Ordered, func() { return "", &oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound} } vol.ServerId = new(instanceID) - vol.Status = new("attached") + vol.Status = new("ATTACHED") return *vol.Id, nil }).AnyTimes() @@ -343,7 +343,15 @@ var _ = Describe("CSI sanity test", Ordered, func() { gomock.Any(), // context gomock.Any(), // instanceID gomock.Any(), // volumeID - ).Return(nil).AnyTimes() + ).DoAndReturn(func(_ context.Context, _, volumeID string) error { + vol, ok := createdVolumes[volumeID] + if !ok { + return &oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound} + } + vol.ServerId = nil + vol.Status = new("AVAILABLE") + return nil + }).AnyTimes() iaasClient.EXPECT().WaitDiskDetached( gomock.Any(), // context