From 97ef671a35215fc793893a1859e62d54ab1502b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Mon, 16 Dec 2024 18:18:10 +0000 Subject: [PATCH 1/3] feat: Move image waiters from iaasalpha to iaas --- services/iaas/wait/wait.go | 54 +++++++++++++ services/iaas/wait/wait_test.go | 133 ++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) diff --git a/services/iaas/wait/wait.go b/services/iaas/wait/wait.go index bd1457bd..64237c53 100644 --- a/services/iaas/wait/wait.go +++ b/services/iaas/wait/wait.go @@ -9,6 +9,7 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/wait" "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha" ) const ( @@ -24,6 +25,8 @@ const ( ServerDeallocatedStatus = "DEALLOCATED" ServerRescueStatus = "RESCUE" + ImageAvailableStatus = "AVAILABLE" + RequestCreateAction = "CREATE" RequestUpdateAction = "UPDATE" RequestDeleteAction = "DELETE" @@ -43,6 +46,7 @@ type APIClientInterface interface { GetVolumeExecute(ctx context.Context, projectId string, volumeId string) (*iaas.Volume, error) GetServerExecute(ctx context.Context, projectId string, serverId string) (*iaas.Server, error) GetAttachedVolumeExecute(ctx context.Context, projectId string, serverId string, volumeId string) (*iaas.VolumeAttachment, error) + GetImageExecute(ctx context.Context, projectId string, imageId string) (*iaasalpha.Image, error) } // CreateNetworkAreaWaitHandler will wait for network area creation @@ -546,3 +550,53 @@ func RemoveVolumeFromServerWaitHandler(ctx context.Context, a APIClientInterface handler.SetTimeout(10 * time.Minute) return handler } + +// UploadImageWaitHandler will wait for the status image to become AVAILABLE, which indicates the upload of the image has been completed successfully +func UploadImageWaitHandler(ctx context.Context, a APIClientInterface, projectId, imageId string) *wait.AsyncActionHandler[iaasalpha.Image] { + handler := wait.New(func() (waitFinished bool, response *iaasalpha.Image, err error) { + image, err := a.GetImageExecute(ctx, projectId, imageId) + if err != nil { + return false, image, err + } + if image.Id == nil || image.Status == nil { + return false, image, fmt.Errorf("upload failed for image with id %s, the response is not valid: the id or the status are missing", imageId) + } + if *image.Id == imageId && *image.Status == ImageAvailableStatus { + return true, image, nil + } + if *image.Id == imageId && *image.Status == ErrorStatus { + return true, image, fmt.Errorf("upload failed for image with id %s", imageId) + } + return false, image, nil + }) + handler.SetTimeout(45 * time.Minute) + return handler +} + +// DeleteImageWaitHandler will wait for image deletion +func DeleteImageWaitHandler(ctx context.Context, a APIClientInterface, projectId, imageId string) *wait.AsyncActionHandler[iaasalpha.Image] { + handler := wait.New(func() (waitFinished bool, response *iaasalpha.Image, err error) { + image, err := a.GetImageExecute(ctx, projectId, imageId) + if err == nil { + if image != nil { + if image.Id == nil || image.Status == nil { + return false, image, fmt.Errorf("delete failed for image with id %s, the response is not valid: the id or the status are missing", imageId) + } + if *image.Id == imageId && *image.Status == DeleteSuccess { + return true, image, nil + } + } + return false, nil, nil + } + oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped + if !ok { + return false, image, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err) + } + if oapiErr.StatusCode != http.StatusNotFound { + return false, image, err + } + return true, nil, nil + }) + handler.SetTimeout(15 * time.Minute) + return handler +} diff --git a/services/iaas/wait/wait_test.go b/services/iaas/wait/wait_test.go index f4661614..54a3438b 100644 --- a/services/iaas/wait/wait_test.go +++ b/services/iaas/wait/wait_test.go @@ -9,6 +9,7 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha" ) type apiClientMocked struct { @@ -20,6 +21,7 @@ type apiClientMocked struct { getVolumeFails bool getServerFails bool getAttachedVolumeFails bool + getImageFails bool isAttached bool requestAction string returnResizing bool @@ -142,6 +144,25 @@ func (a *apiClientMocked) GetAttachedVolumeExecute(_ context.Context, _, _, _ st }, nil } +func (a *apiClientMocked) GetImageExecute(_ context.Context, _, _ string) (*iaasalpha.Image, error) { + if a.getImageFails { + return nil, &oapierror.GenericOpenAPIError{ + StatusCode: 500, + } + } + + if a.isDeleted { + return nil, &oapierror.GenericOpenAPIError{ + StatusCode: 404, + } + } + + return &iaasalpha.Image{ + Id: utils.Ptr("iid"), + Status: &a.resourceState, + }, nil +} + func TestCreateNetworkAreaWaitHandler(t *testing.T) { tests := []struct { desc string @@ -1372,3 +1393,115 @@ func TestRemoveVolumeFromServerWaitHandler(t *testing.T) { }) } } + +func TestUploadImageWaitHandler(t *testing.T) { + tests := []struct { + desc string + getFails bool + resourceState string + wantErr bool + wantResp bool + }{ + { + desc: "upload_succeeded", + getFails: false, + resourceState: ImageAvailableStatus, + wantErr: false, + wantResp: true, + }, + { + desc: "error_status", + getFails: false, + resourceState: ErrorStatus, + wantErr: true, + wantResp: true, + }, + { + desc: "get_fails", + getFails: true, + resourceState: "", + wantErr: true, + wantResp: false, + }, + { + desc: "timeout", + getFails: false, + resourceState: "ANOTHER Status", + wantErr: true, + wantResp: true, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + apiClient := &apiClientMocked{ + getImageFails: tt.getFails, + resourceState: tt.resourceState, + } + + var wantRes *iaasalpha.Image + if tt.wantResp { + wantRes = &iaasalpha.Image{ + Id: utils.Ptr("iid"), + Status: utils.Ptr(tt.resourceState), + } + } + + handler := UploadImageWaitHandler(context.Background(), apiClient, "pid", "iid") + + gotRes, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background()) + + if (err != nil) != tt.wantErr { + t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) + } + if !cmp.Equal(gotRes, wantRes) { + t.Fatalf("handler gotRes = %v, want %v", gotRes, wantRes) + } + }) + } +} + +func TestDeleteImageWaitHandler(t *testing.T) { + tests := []struct { + desc string + getFails bool + isDeleted bool + resourceState string + wantErr bool + }{ + { + desc: "delete_succeeded", + getFails: false, + isDeleted: true, + wantErr: false, + }, + { + desc: "get_fails", + getFails: true, + resourceState: "", + wantErr: true, + }, + { + desc: "timeout", + getFails: false, + resourceState: "ANOTHER Status", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + apiClient := &apiClientMocked{ + getImageFails: tt.getFails, + isDeleted: tt.isDeleted, + resourceState: tt.resourceState, + } + + handler := DeleteImageWaitHandler(context.Background(), apiClient, "pid", "iid") + + _, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background()) + + if (err != nil) != tt.wantErr { + t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From 3d8214fb0d28203ef4439aee357974bb957342ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Mon, 16 Dec 2024 18:20:53 +0000 Subject: [PATCH 2/3] feat: Add changelogs --- CHANGELOG.md | 2 ++ services/iaas/CHANGELOG.md | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bda7d59a..9213aa4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ > > Use `github.com/stackitcloud/stackit-sdk-go/services/authorization` instead. +- `iaas`: [v0.18](services/iaas/CHANGELOG.md#v0180-2024-12-16) + - **Feature:** Add waiters for async operations: `UploadImageWaitHandler` and `DeleteImageWaitHandler` - `iaas`: [v0.17.0](services/iaas/CHANGELOG.md#v0170-2024-12-16) - **Feature:** Add new methods to manage affinity groups: `CreateAffinityGroup`, `DeleteAffinityGroup`, `GetAffinityGroup`, and `ListAffinityGroup` - **Feature:** Add new methods to manage backups: `CreateBackup`, `DeleteBackup`, `GetBackup`, `ListBackup`, `RestoreBackup`, `ExecuteBackup`,`UpdateBackup` diff --git a/services/iaas/CHANGELOG.md b/services/iaas/CHANGELOG.md index 460290fa..7d9d448c 100644 --- a/services/iaas/CHANGELOG.md +++ b/services/iaas/CHANGELOG.md @@ -1,3 +1,7 @@ +## v0.18.0 (2024-12-16) + +- **Feature:** Add waiters for async operations: `UploadImageWaitHandler` and `DeleteImageWaitHandler` + ## v0.17.0 (2024-12-16) - **Feature:** Add new methods to manage affinity groups: `CreateAffinityGroup`, `DeleteAffinityGroup`, `GetAffinityGroup`, and `ListAffinityGroup` From 0f7502c092e608df468e4910fdba6f2be5bfb4b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Palet?= Date: Mon, 16 Dec 2024 18:28:37 +0000 Subject: [PATCH 3/3] fix: IaaS dependency in waiters --- services/iaas/wait/wait.go | 11 +++++------ services/iaas/wait/wait_test.go | 9 ++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/services/iaas/wait/wait.go b/services/iaas/wait/wait.go index 64237c53..65aa812b 100644 --- a/services/iaas/wait/wait.go +++ b/services/iaas/wait/wait.go @@ -9,7 +9,6 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/wait" "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha" ) const ( @@ -46,7 +45,7 @@ type APIClientInterface interface { GetVolumeExecute(ctx context.Context, projectId string, volumeId string) (*iaas.Volume, error) GetServerExecute(ctx context.Context, projectId string, serverId string) (*iaas.Server, error) GetAttachedVolumeExecute(ctx context.Context, projectId string, serverId string, volumeId string) (*iaas.VolumeAttachment, error) - GetImageExecute(ctx context.Context, projectId string, imageId string) (*iaasalpha.Image, error) + GetImageExecute(ctx context.Context, projectId string, imageId string) (*iaas.Image, error) } // CreateNetworkAreaWaitHandler will wait for network area creation @@ -552,8 +551,8 @@ func RemoveVolumeFromServerWaitHandler(ctx context.Context, a APIClientInterface } // UploadImageWaitHandler will wait for the status image to become AVAILABLE, which indicates the upload of the image has been completed successfully -func UploadImageWaitHandler(ctx context.Context, a APIClientInterface, projectId, imageId string) *wait.AsyncActionHandler[iaasalpha.Image] { - handler := wait.New(func() (waitFinished bool, response *iaasalpha.Image, err error) { +func UploadImageWaitHandler(ctx context.Context, a APIClientInterface, projectId, imageId string) *wait.AsyncActionHandler[iaas.Image] { + handler := wait.New(func() (waitFinished bool, response *iaas.Image, err error) { image, err := a.GetImageExecute(ctx, projectId, imageId) if err != nil { return false, image, err @@ -574,8 +573,8 @@ func UploadImageWaitHandler(ctx context.Context, a APIClientInterface, projectId } // DeleteImageWaitHandler will wait for image deletion -func DeleteImageWaitHandler(ctx context.Context, a APIClientInterface, projectId, imageId string) *wait.AsyncActionHandler[iaasalpha.Image] { - handler := wait.New(func() (waitFinished bool, response *iaasalpha.Image, err error) { +func DeleteImageWaitHandler(ctx context.Context, a APIClientInterface, projectId, imageId string) *wait.AsyncActionHandler[iaas.Image] { + handler := wait.New(func() (waitFinished bool, response *iaas.Image, err error) { image, err := a.GetImageExecute(ctx, projectId, imageId) if err == nil { if image != nil { diff --git a/services/iaas/wait/wait_test.go b/services/iaas/wait/wait_test.go index 54a3438b..d2cc55eb 100644 --- a/services/iaas/wait/wait_test.go +++ b/services/iaas/wait/wait_test.go @@ -9,7 +9,6 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/services/iaas" - "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha" ) type apiClientMocked struct { @@ -144,7 +143,7 @@ func (a *apiClientMocked) GetAttachedVolumeExecute(_ context.Context, _, _, _ st }, nil } -func (a *apiClientMocked) GetImageExecute(_ context.Context, _, _ string) (*iaasalpha.Image, error) { +func (a *apiClientMocked) GetImageExecute(_ context.Context, _, _ string) (*iaas.Image, error) { if a.getImageFails { return nil, &oapierror.GenericOpenAPIError{ StatusCode: 500, @@ -157,7 +156,7 @@ func (a *apiClientMocked) GetImageExecute(_ context.Context, _, _ string) (*iaas } } - return &iaasalpha.Image{ + return &iaas.Image{ Id: utils.Ptr("iid"), Status: &a.resourceState, }, nil @@ -1438,9 +1437,9 @@ func TestUploadImageWaitHandler(t *testing.T) { resourceState: tt.resourceState, } - var wantRes *iaasalpha.Image + var wantRes *iaas.Image if tt.wantResp { - wantRes = &iaasalpha.Image{ + wantRes = &iaas.Image{ Id: utils.Ptr("iid"), Status: utils.Ptr(tt.resourceState), }