Skip to content

Commit 8fe4ba3

Browse files
authored
Add waiters for image operations (IaaS) (#1245)
* feat: Move image waiters from iaasalpha to iaas * feat: Add changelogs * fix: IaaS dependency in waiters
1 parent 82838e1 commit 8fe4ba3

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
>
77
> Use `github.com/stackitcloud/stackit-sdk-go/services/authorization` instead.
88
9+
- `iaas`: [v0.18](services/iaas/CHANGELOG.md#v0180-2024-12-16)
10+
- **Feature:** Add waiters for async operations: `UploadImageWaitHandler` and `DeleteImageWaitHandler`
911
- `iaas`: [v0.17.0](services/iaas/CHANGELOG.md#v0170-2024-12-16)
1012
- **Feature:** Add new methods to manage affinity groups: `CreateAffinityGroup`, `DeleteAffinityGroup`, `GetAffinityGroup`, and `ListAffinityGroup`
1113
- **Feature:** Add new methods to manage backups: `CreateBackup`, `DeleteBackup`, `GetBackup`, `ListBackup`, `RestoreBackup`, `ExecuteBackup`,`UpdateBackup`

services/iaas/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## v0.18.0 (2024-12-16)
2+
3+
- **Feature:** Add waiters for async operations: `UploadImageWaitHandler` and `DeleteImageWaitHandler`
4+
15
## v0.17.0 (2024-12-16)
26

37
- **Feature:** Add new methods to manage affinity groups: `CreateAffinityGroup`, `DeleteAffinityGroup`, `GetAffinityGroup`, and `ListAffinityGroup`

services/iaas/wait/wait.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const (
2424
ServerDeallocatedStatus = "DEALLOCATED"
2525
ServerRescueStatus = "RESCUE"
2626

27+
ImageAvailableStatus = "AVAILABLE"
28+
2729
RequestCreateAction = "CREATE"
2830
RequestUpdateAction = "UPDATE"
2931
RequestDeleteAction = "DELETE"
@@ -43,6 +45,7 @@ type APIClientInterface interface {
4345
GetVolumeExecute(ctx context.Context, projectId string, volumeId string) (*iaas.Volume, error)
4446
GetServerExecute(ctx context.Context, projectId string, serverId string) (*iaas.Server, error)
4547
GetAttachedVolumeExecute(ctx context.Context, projectId string, serverId string, volumeId string) (*iaas.VolumeAttachment, error)
48+
GetImageExecute(ctx context.Context, projectId string, imageId string) (*iaas.Image, error)
4649
}
4750

4851
// CreateNetworkAreaWaitHandler will wait for network area creation
@@ -546,3 +549,53 @@ func RemoveVolumeFromServerWaitHandler(ctx context.Context, a APIClientInterface
546549
handler.SetTimeout(10 * time.Minute)
547550
return handler
548551
}
552+
553+
// UploadImageWaitHandler will wait for the status image to become AVAILABLE, which indicates the upload of the image has been completed successfully
554+
func UploadImageWaitHandler(ctx context.Context, a APIClientInterface, projectId, imageId string) *wait.AsyncActionHandler[iaas.Image] {
555+
handler := wait.New(func() (waitFinished bool, response *iaas.Image, err error) {
556+
image, err := a.GetImageExecute(ctx, projectId, imageId)
557+
if err != nil {
558+
return false, image, err
559+
}
560+
if image.Id == nil || image.Status == nil {
561+
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)
562+
}
563+
if *image.Id == imageId && *image.Status == ImageAvailableStatus {
564+
return true, image, nil
565+
}
566+
if *image.Id == imageId && *image.Status == ErrorStatus {
567+
return true, image, fmt.Errorf("upload failed for image with id %s", imageId)
568+
}
569+
return false, image, nil
570+
})
571+
handler.SetTimeout(45 * time.Minute)
572+
return handler
573+
}
574+
575+
// DeleteImageWaitHandler will wait for image deletion
576+
func DeleteImageWaitHandler(ctx context.Context, a APIClientInterface, projectId, imageId string) *wait.AsyncActionHandler[iaas.Image] {
577+
handler := wait.New(func() (waitFinished bool, response *iaas.Image, err error) {
578+
image, err := a.GetImageExecute(ctx, projectId, imageId)
579+
if err == nil {
580+
if image != nil {
581+
if image.Id == nil || image.Status == nil {
582+
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)
583+
}
584+
if *image.Id == imageId && *image.Status == DeleteSuccess {
585+
return true, image, nil
586+
}
587+
}
588+
return false, nil, nil
589+
}
590+
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
591+
if !ok {
592+
return false, image, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err)
593+
}
594+
if oapiErr.StatusCode != http.StatusNotFound {
595+
return false, image, err
596+
}
597+
return true, nil, nil
598+
})
599+
handler.SetTimeout(15 * time.Minute)
600+
return handler
601+
}

services/iaas/wait/wait_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type apiClientMocked struct {
2020
getVolumeFails bool
2121
getServerFails bool
2222
getAttachedVolumeFails bool
23+
getImageFails bool
2324
isAttached bool
2425
requestAction string
2526
returnResizing bool
@@ -142,6 +143,25 @@ func (a *apiClientMocked) GetAttachedVolumeExecute(_ context.Context, _, _, _ st
142143
}, nil
143144
}
144145

146+
func (a *apiClientMocked) GetImageExecute(_ context.Context, _, _ string) (*iaas.Image, error) {
147+
if a.getImageFails {
148+
return nil, &oapierror.GenericOpenAPIError{
149+
StatusCode: 500,
150+
}
151+
}
152+
153+
if a.isDeleted {
154+
return nil, &oapierror.GenericOpenAPIError{
155+
StatusCode: 404,
156+
}
157+
}
158+
159+
return &iaas.Image{
160+
Id: utils.Ptr("iid"),
161+
Status: &a.resourceState,
162+
}, nil
163+
}
164+
145165
func TestCreateNetworkAreaWaitHandler(t *testing.T) {
146166
tests := []struct {
147167
desc string
@@ -1372,3 +1392,115 @@ func TestRemoveVolumeFromServerWaitHandler(t *testing.T) {
13721392
})
13731393
}
13741394
}
1395+
1396+
func TestUploadImageWaitHandler(t *testing.T) {
1397+
tests := []struct {
1398+
desc string
1399+
getFails bool
1400+
resourceState string
1401+
wantErr bool
1402+
wantResp bool
1403+
}{
1404+
{
1405+
desc: "upload_succeeded",
1406+
getFails: false,
1407+
resourceState: ImageAvailableStatus,
1408+
wantErr: false,
1409+
wantResp: true,
1410+
},
1411+
{
1412+
desc: "error_status",
1413+
getFails: false,
1414+
resourceState: ErrorStatus,
1415+
wantErr: true,
1416+
wantResp: true,
1417+
},
1418+
{
1419+
desc: "get_fails",
1420+
getFails: true,
1421+
resourceState: "",
1422+
wantErr: true,
1423+
wantResp: false,
1424+
},
1425+
{
1426+
desc: "timeout",
1427+
getFails: false,
1428+
resourceState: "ANOTHER Status",
1429+
wantErr: true,
1430+
wantResp: true,
1431+
},
1432+
}
1433+
for _, tt := range tests {
1434+
t.Run(tt.desc, func(t *testing.T) {
1435+
apiClient := &apiClientMocked{
1436+
getImageFails: tt.getFails,
1437+
resourceState: tt.resourceState,
1438+
}
1439+
1440+
var wantRes *iaas.Image
1441+
if tt.wantResp {
1442+
wantRes = &iaas.Image{
1443+
Id: utils.Ptr("iid"),
1444+
Status: utils.Ptr(tt.resourceState),
1445+
}
1446+
}
1447+
1448+
handler := UploadImageWaitHandler(context.Background(), apiClient, "pid", "iid")
1449+
1450+
gotRes, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background())
1451+
1452+
if (err != nil) != tt.wantErr {
1453+
t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr)
1454+
}
1455+
if !cmp.Equal(gotRes, wantRes) {
1456+
t.Fatalf("handler gotRes = %v, want %v", gotRes, wantRes)
1457+
}
1458+
})
1459+
}
1460+
}
1461+
1462+
func TestDeleteImageWaitHandler(t *testing.T) {
1463+
tests := []struct {
1464+
desc string
1465+
getFails bool
1466+
isDeleted bool
1467+
resourceState string
1468+
wantErr bool
1469+
}{
1470+
{
1471+
desc: "delete_succeeded",
1472+
getFails: false,
1473+
isDeleted: true,
1474+
wantErr: false,
1475+
},
1476+
{
1477+
desc: "get_fails",
1478+
getFails: true,
1479+
resourceState: "",
1480+
wantErr: true,
1481+
},
1482+
{
1483+
desc: "timeout",
1484+
getFails: false,
1485+
resourceState: "ANOTHER Status",
1486+
wantErr: true,
1487+
},
1488+
}
1489+
for _, tt := range tests {
1490+
t.Run(tt.desc, func(t *testing.T) {
1491+
apiClient := &apiClientMocked{
1492+
getImageFails: tt.getFails,
1493+
isDeleted: tt.isDeleted,
1494+
resourceState: tt.resourceState,
1495+
}
1496+
1497+
handler := DeleteImageWaitHandler(context.Background(), apiClient, "pid", "iid")
1498+
1499+
_, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background())
1500+
1501+
if (err != nil) != tt.wantErr {
1502+
t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr)
1503+
}
1504+
})
1505+
}
1506+
}

0 commit comments

Comments
 (0)