Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions services/iaasalpha/wait/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,23 @@ const (
ErrorStatus = "ERROR"
ServerActiveStatus = "ACTIVE"
ServerResizingStatus = "RESIZING"

RequestCreateAction = "CREATE"
RequestUpdateAction = "UPDATE"
RequestDeleteAction = "DELETE"
RequestCreatedStatus = "CREATED"
RequestUpdatedStatus = "UPDATED"
RequestDeletedStatus = "DELETED"
RequestFailedStatus = "FAILED"

XRequestIDHeader = "X-Request-Id"
)

// Interfaces needed for tests
type APIClientInterface interface {
GetVolumeExecute(ctx context.Context, projectId string, volumeId string) (*iaasalpha.Volume, error)
GetServerExecute(ctx context.Context, projectId string, serverId string) (*iaasalpha.Server, error)
GetProjectRequestExecute(ctx context.Context, projectId string, requestId string) (*iaasalpha.Request, error)
}

// CreateVolumeWaitHandler will wait for volume creation
Expand Down Expand Up @@ -165,3 +176,59 @@ func DeleteServerWaitHandler(ctx context.Context, a APIClientInterface, projectI
handler.SetTimeout(20 * time.Minute)
return handler
}

// ProjectRequestWaitHandler will wait for a request to succeed
// It receives a request id that can be obtained from the x-request-id header in the http response of any operation in the IaaS API.
// To get the raw http response of an operation using the SDK, use the runtime.WithCaptureHTTPResponse method from the core pkg.
// Then the value of the request id can be obtained by accessing the header key which is defined in the constant XRequestIDHeader in this package.
// Example usage:
// var httpResp *http.Response
// ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(context.Background(), &httpResp)
// err = iaasalphaClient.AddPublicIpToServer(ctxWithHTTPResp, projectId, serverId, publicIpId).Execute()
// requestId := httpResp.Header[wait.XRequestIDHeader][0]
// _, err = wait.ProjectRequestWaitHandler(context.Background(), iaasalphaClient, projectId, requestId).WaitWithContext(context.Background())
func ProjectRequestWaitHandler(ctx context.Context, a APIClientInterface, projectId, requestId string) *wait.AsyncActionHandler[iaasalpha.Request] {
handler := wait.New(func() (waitFinished bool, response *iaasalpha.Request, err error) {
request, err := a.GetProjectRequestExecute(ctx, projectId, requestId)
if err != nil {
return false, request, err
}

if request == nil {
return false, request, fmt.Errorf("request failed for request with id %s: nil response from GetProjectRequestExecute", requestId)
}

if request.RequestId == nil || request.RequestAction == nil || request.Status == nil {
return false, request, fmt.Errorf("request failed for request with id %s, the response is not valid: the id, the request action or the status are missing", requestId)
}

if *request.RequestId != requestId {
return false, request, fmt.Errorf("request failed for request with id %s: the response id doesn't match the request id", requestId)
}

switch *request.RequestAction {
case RequestCreateAction:
if *request.Status == RequestCreatedStatus {
return true, request, nil
}
case RequestUpdateAction:
if *request.Status == RequestUpdatedStatus {
return true, request, nil
}
case RequestDeleteAction:
if *request.Status == RequestDeletedStatus {
return true, request, nil
}
default:
return false, request, fmt.Errorf("request failed for request with id %s, the request action %s is not supported", requestId, *request.RequestAction)
}

if *request.Status == RequestFailedStatus {
return true, request, fmt.Errorf("request failed for request with id %s", requestId)
}

return false, request, nil
})
handler.SetTimeout(20 * time.Minute)
return handler
}
121 changes: 116 additions & 5 deletions services/iaasalpha/wait/wait_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import (
)

type apiClientMocked struct {
getVolumeFails bool
getServerFails bool
isDeleted bool
resourceState string
returnResizing bool
getVolumeFails bool
getServerFails bool
getProjectRequestFails bool
isDeleted bool
resourceState string
requestAction string
returnResizing bool
}

func (a *apiClientMocked) GetVolumeExecute(_ context.Context, _, _ string) (*iaasalpha.Volume, error) {
Expand Down Expand Up @@ -65,6 +67,20 @@ func (a *apiClientMocked) GetServerExecute(_ context.Context, _, _ string) (*iaa
}, nil
}

func (a *apiClientMocked) GetProjectRequestExecute(_ context.Context, _, _ string) (*iaasalpha.Request, error) {
if a.getProjectRequestFails {
return nil, &oapierror.GenericOpenAPIError{
StatusCode: 500,
}
}

return &iaasalpha.Request{
RequestId: utils.Ptr("rid"),
RequestAction: &a.requestAction,
Status: &a.resourceState,
}, nil
}

func TestCreateVolumeWaitHandler(t *testing.T) {
tests := []struct {
desc string
Expand Down Expand Up @@ -396,3 +412,98 @@ func TestResizeServerWaitHandler(t *testing.T) {
})
}
}

func TestProjectRequestWaitHandler(t *testing.T) {
tests := []struct {
desc string
getFails bool
requestState string
requestAction string
wantErr bool
wantResp bool
}{
{
desc: "create_succeeded",
getFails: false,
requestAction: RequestCreateAction,
requestState: RequestCreatedStatus,
wantErr: false,
wantResp: true,
},
{
desc: "update_succeeded",
getFails: false,
requestAction: RequestUpdateAction,
requestState: RequestUpdatedStatus,
wantErr: false,
wantResp: true,
},
{
desc: "delete_succeeded",
getFails: false,
requestAction: RequestDeleteAction,
requestState: RequestDeletedStatus,
wantErr: false,
wantResp: true,
},
{
desc: "unsupported_action",
getFails: false,
requestAction: "OTHER_ACTION",
wantErr: true,
wantResp: true,
},
{
desc: "error_status",
getFails: false,
requestAction: RequestCreateAction,
requestState: ErrorStatus,
wantErr: true,
wantResp: true,
},
{
desc: "get_fails",
getFails: true,
requestState: "",
wantErr: true,
wantResp: false,
},
{
desc: "timeout",
getFails: false,
requestAction: RequestCreateAction,
requestState: "ANOTHER Status",
wantErr: true,
wantResp: true,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
apiClient := &apiClientMocked{
getProjectRequestFails: tt.getFails,
requestAction: tt.requestAction,
resourceState: tt.requestState,
}

var wantRes *iaasalpha.Request
if tt.wantResp {
wantRes = &iaasalpha.Request{
RequestId: utils.Ptr("rid"),
RequestAction: &tt.requestAction,
Status: &tt.requestState,
}
}

handler := ProjectRequestWaitHandler(context.Background(), apiClient, "pid", "rid")

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)
}
})
}
}
Loading