Skip to content

Commit ce35926

Browse files
authored
feat: Add waiters for Virtual IP operations (iaasalpha) (#1012)
1 parent c23b9e2 commit ce35926

File tree

3 files changed

+280
-4
lines changed

3 files changed

+280
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
"github.com/stackitcloud/stackit-sdk-go/core/config"
9+
"github.com/stackitcloud/stackit-sdk-go/core/utils"
10+
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
11+
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha/wait"
12+
)
13+
14+
func main() {
15+
// Specify the project ID and network ID
16+
projectId := "PROJECT_ID"
17+
networkId := "NETWORK_ID"
18+
19+
// Create a new API client, that uses default authentication and configuration
20+
iaasalphaClient, err := iaasalpha.NewAPIClient(
21+
config.WithRegion("eu01"),
22+
)
23+
if err != nil {
24+
fmt.Fprintf(os.Stderr, "[iaasalpha API] Creating API client: %v\n", err)
25+
os.Exit(1)
26+
}
27+
28+
virtualIPs, err := iaasalphaClient.ListVirtualIPs(context.Background(), projectId, networkId).Execute()
29+
if err != nil {
30+
fmt.Fprintf(os.Stderr, "[iaasalpha API] Error when calling `ListVirtualIPs`: %v\n", err)
31+
} else {
32+
fmt.Printf("[iaasalpha API] Number of virtual IPs: %v\n", len(*virtualIPs.Items))
33+
}
34+
35+
// Create a virtual IP
36+
createVirtualIPPayload := iaasalpha.CreateVirtualIPPayload{
37+
Name: utils.Ptr("example-vip"),
38+
Labels: &map[string]interface{}{
39+
"key": "value",
40+
},
41+
}
42+
virtualIP, err := iaasalphaClient.CreateVirtualIP(context.Background(), projectId, networkId).CreateVirtualIPPayload(createVirtualIPPayload).Execute()
43+
if err != nil {
44+
fmt.Fprintf(os.Stderr, "[iaasalpha API] Error when calling `CreateVirtualIP`: %v\n", err)
45+
} else {
46+
fmt.Printf("[iaasalpha API] Triggered creation of virtual IP with ID %q.\n", *virtualIP.Id)
47+
}
48+
49+
// Wait for creation of the virtual IP
50+
virtualIP, err = wait.CreateVirtualIPWaitHandler(context.Background(), iaasalphaClient, projectId, networkId, *virtualIP.Id).WaitWithContext(context.Background())
51+
if err != nil {
52+
fmt.Fprintf(os.Stderr, "[iaasalpha API] Error when waiting for creation: %v\n", err)
53+
os.Exit(1)
54+
}
55+
56+
fmt.Printf("[iaasalpha API] Virtual IP %q has been successfully created.\n", *virtualIP.Id)
57+
58+
// Delete a virtual IP
59+
err = iaasalphaClient.DeleteVirtualIP(context.Background(), projectId, networkId, *virtualIP.Id).Execute()
60+
if err != nil {
61+
fmt.Fprintf(os.Stderr, "[iaasalpha API] Error when calling `DeleteVirtualIP`: %v\n", err)
62+
} else {
63+
fmt.Printf("[iaasalpha API] Triggered deletion of virtual IP with ID %q.\n", *virtualIP.Id)
64+
}
65+
66+
// Wait for deletion of the virtual IP
67+
_, err = wait.DeleteVirtualIPWaitHandler(context.Background(), iaasalphaClient, projectId, networkId, *virtualIP.Id).WaitWithContext(context.Background())
68+
if err != nil {
69+
fmt.Fprintf(os.Stderr, "[iaasalpha API] Error when waiting for deletion: %v\n", err)
70+
os.Exit(1)
71+
}
72+
73+
fmt.Printf("[iaasalpha API] Virtual IP %q has been successfully deleted.\n", *virtualIP.Id)
74+
}

services/iaasalpha/wait/wait.go

+59-4
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ import (
1212
)
1313

1414
const (
15+
DeleteSuccess = "DELETED"
16+
ErrorStatus = "ERROR"
17+
1518
VolumeAvailableStatus = "AVAILABLE"
16-
DeleteSuccess = "DELETED"
17-
ErrorStatus = "ERROR"
18-
ServerActiveStatus = "ACTIVE"
19-
ServerResizingStatus = "RESIZING"
19+
20+
ServerActiveStatus = "ACTIVE"
21+
ServerResizingStatus = "RESIZING"
22+
23+
VirtualIpCreatedStatus = "CREATED"
2024

2125
RequestCreateAction = "CREATE"
2226
RequestUpdateAction = "UPDATE"
@@ -35,6 +39,7 @@ type APIClientInterface interface {
3539
GetServerExecute(ctx context.Context, projectId string, serverId string) (*iaasalpha.Server, error)
3640
GetProjectRequestExecute(ctx context.Context, projectId string, requestId string) (*iaasalpha.Request, error)
3741
GetAttachedVolumeExecute(ctx context.Context, projectId string, serverId string, volumeId string) (*iaasalpha.VolumeAttachment, error)
42+
GetVirtualIPExecute(ctx context.Context, projectId string, networkId string, virtualIpId string) (*iaasalpha.VirtualIp, error)
3843
}
3944

4045
// CreateVolumeWaitHandler will wait for volume creation
@@ -291,3 +296,53 @@ func RemoveVolumeFromServerWaitHandler(ctx context.Context, a APIClientInterface
291296
handler.SetTimeout(10 * time.Minute)
292297
return handler
293298
}
299+
300+
// CreateVirtualIPWaitHandler will wait for server creation
301+
func CreateVirtualIPWaitHandler(ctx context.Context, a APIClientInterface, projectId, networkId, virtualIpId string) *wait.AsyncActionHandler[iaasalpha.VirtualIp] {
302+
handler := wait.New(func() (waitFinished bool, response *iaasalpha.VirtualIp, err error) {
303+
virtualIp, err := a.GetVirtualIPExecute(ctx, projectId, networkId, virtualIpId)
304+
if err != nil {
305+
return false, virtualIp, err
306+
}
307+
if virtualIp.Id == nil || virtualIp.Status == nil {
308+
return false, virtualIp, fmt.Errorf("create failed for virtual ip with id %s, the response is not valid: the id or the status are missing", networkId)
309+
}
310+
if *virtualIp.Id == virtualIpId && *virtualIp.Status == VirtualIpCreatedStatus {
311+
return true, virtualIp, nil
312+
}
313+
if *virtualIp.Id == virtualIpId && *virtualIp.Status == ErrorStatus {
314+
return true, virtualIp, fmt.Errorf("create failed for virtual ip with id %s", networkId)
315+
}
316+
return false, virtualIp, nil
317+
})
318+
handler.SetTimeout(15 * time.Minute)
319+
return handler
320+
}
321+
322+
// DeleteVirtualIPWaitHandler will wait for volume deletion
323+
func DeleteVirtualIPWaitHandler(ctx context.Context, a APIClientInterface, projectId, networkId, virtualIpId string) *wait.AsyncActionHandler[iaasalpha.VirtualIp] {
324+
handler := wait.New(func() (waitFinished bool, response *iaasalpha.VirtualIp, err error) {
325+
virtualIp, err := a.GetVirtualIPExecute(ctx, projectId, networkId, virtualIpId)
326+
if err == nil {
327+
if virtualIp != nil {
328+
if virtualIp.Id == nil || virtualIp.Status == nil {
329+
return false, virtualIp, fmt.Errorf("delete failed for virtual ip with id %s, the response is not valid: the id or the status are missing", virtualIpId)
330+
}
331+
if *virtualIp.Id == virtualIpId && *virtualIp.Status == DeleteSuccess {
332+
return true, virtualIp, nil
333+
}
334+
}
335+
return false, nil, nil
336+
}
337+
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
338+
if !ok {
339+
return false, virtualIp, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err)
340+
}
341+
if oapiErr.StatusCode != http.StatusNotFound {
342+
return false, virtualIp, err
343+
}
344+
return true, nil, nil
345+
})
346+
handler.SetTimeout(15 * time.Minute)
347+
return handler
348+
}

services/iaasalpha/wait/wait_test.go

+147
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type apiClientMocked struct {
1616
getServerFails bool
1717
getProjectRequestFails bool
1818
getAttachedVolumeFails bool
19+
getVirtualIPFails bool
1920
isDeleted bool
2021
isAttached bool
2122
resourceState string
@@ -102,6 +103,25 @@ func (a *apiClientMocked) GetAttachedVolumeExecute(_ context.Context, _, _, _ st
102103
}, nil
103104
}
104105

106+
func (a *apiClientMocked) GetVirtualIPExecute(_ context.Context, _, _, _ string) (*iaasalpha.VirtualIp, error) {
107+
if a.getVirtualIPFails {
108+
return nil, &oapierror.GenericOpenAPIError{
109+
StatusCode: 500,
110+
}
111+
}
112+
113+
if a.isDeleted {
114+
return nil, &oapierror.GenericOpenAPIError{
115+
StatusCode: 404,
116+
}
117+
}
118+
119+
return &iaasalpha.VirtualIp{
120+
Id: utils.Ptr("vipid"),
121+
Status: &a.resourceState,
122+
}, nil
123+
}
124+
105125
func TestCreateVolumeWaitHandler(t *testing.T) {
106126
tests := []struct {
107127
desc string
@@ -644,3 +664,130 @@ func TestRemoveVolumeFromServerWaitHandler(t *testing.T) {
644664
})
645665
}
646666
}
667+
668+
func TestCreateVirtualIPWaitHandler(t *testing.T) {
669+
tests := []struct {
670+
desc string
671+
getFails bool
672+
resourceState string
673+
wantErr bool
674+
wantResp bool
675+
}{
676+
{
677+
desc: "create_succeeded",
678+
getFails: false,
679+
resourceState: VirtualIpCreatedStatus,
680+
wantErr: false,
681+
wantResp: true,
682+
},
683+
{
684+
desc: "error_status",
685+
getFails: false,
686+
resourceState: ErrorStatus,
687+
wantErr: true,
688+
wantResp: true,
689+
},
690+
{
691+
desc: "get_fails",
692+
getFails: true,
693+
resourceState: "",
694+
wantErr: true,
695+
wantResp: false,
696+
},
697+
{
698+
desc: "timeout",
699+
getFails: false,
700+
resourceState: "ANOTHER Status",
701+
wantErr: true,
702+
wantResp: true,
703+
},
704+
}
705+
for _, tt := range tests {
706+
t.Run(tt.desc, func(t *testing.T) {
707+
apiClient := &apiClientMocked{
708+
getVirtualIPFails: tt.getFails,
709+
resourceState: tt.resourceState,
710+
}
711+
712+
var wantRes *iaasalpha.VirtualIp
713+
if tt.wantResp {
714+
wantRes = &iaasalpha.VirtualIp{
715+
Id: utils.Ptr("vipid"),
716+
Status: utils.Ptr(tt.resourceState),
717+
}
718+
}
719+
720+
handler := CreateVirtualIPWaitHandler(context.Background(), apiClient, "pid", "nid", "vipid")
721+
722+
gotRes, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background())
723+
724+
if (err != nil) != tt.wantErr {
725+
t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr)
726+
}
727+
if !cmp.Equal(gotRes, wantRes) {
728+
t.Fatalf("handler gotRes = %v, want %v", gotRes, wantRes)
729+
}
730+
})
731+
}
732+
}
733+
734+
func TestDeleteVirtualIPWaitHandler(t *testing.T) {
735+
tests := []struct {
736+
desc string
737+
getFails bool
738+
isDeleted bool
739+
resourceState string
740+
wantErr bool
741+
wantResp bool
742+
}{
743+
{
744+
desc: "delete_succeeded",
745+
getFails: false,
746+
isDeleted: true,
747+
wantErr: false,
748+
wantResp: false,
749+
},
750+
{
751+
desc: "get_fails",
752+
getFails: true,
753+
resourceState: "",
754+
wantErr: true,
755+
wantResp: false,
756+
},
757+
{
758+
desc: "timeout",
759+
getFails: false,
760+
resourceState: "ANOTHER Status",
761+
wantErr: true,
762+
wantResp: false,
763+
},
764+
}
765+
for _, tt := range tests {
766+
t.Run(tt.desc, func(t *testing.T) {
767+
apiClient := &apiClientMocked{
768+
getVolumeFails: tt.getFails,
769+
isDeleted: tt.isDeleted,
770+
resourceState: tt.resourceState,
771+
}
772+
773+
var wantRes *iaasalpha.VirtualIp
774+
if tt.wantResp {
775+
wantRes = &iaasalpha.VirtualIp{
776+
Id: utils.Ptr("vipid"),
777+
Status: utils.Ptr(tt.resourceState),
778+
}
779+
}
780+
781+
handler := DeleteVirtualIPWaitHandler(context.Background(), apiClient, "pid", "nid", "vipid")
782+
783+
gotRes, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background())
784+
785+
if (err != nil) != tt.wantErr {
786+
t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr)
787+
}
788+
if !cmp.Equal(gotRes, wantRes) {
789+
t.Fatalf("handler gotRes = %v, want %v", gotRes, wantRes)
790+
}
791+
})
792+
}
793+
}

0 commit comments

Comments
 (0)