Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate iaasalpha to iaas: add waiters #937

Merged
merged 2 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## Release (2024-10-21)

- `iaas`: [v0.14.0](services/iaas/CHANGELOG.md#v0140-2024-10-18)
- **Feature:** Add waiter methods for `Volume`, `Server` and `AttachedVolume`

## Release (2024-10-18)

- `iaas`: [v0.13.0](services/iaas/CHANGELOG.md#v0130-2024-10-18)
Expand Down
4 changes: 4 additions & 0 deletions services/iaas/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## v0.14.0 (2024-10-18)

- **Feature:** Add waiter methods for `Volume`, `Server` and `AttachedVolume`

## v0.13.0 (2024-10-18)

- **Feature:** Add support for managing following resources
Expand Down
275 changes: 274 additions & 1 deletion services/iaas/wait/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,32 @@ import (
)

const (
CreateSuccess = "CREATED"
CreateSuccess = "CREATED"
VolumeAvailableStatus = "AVAILABLE"
DeleteSuccess = "DELETED"
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 {
GetNetworkAreaExecute(ctx context.Context, organizationId, areaId string) (*iaas.NetworkArea, error)
GetProjectRequestExecute(ctx context.Context, projectId string, requestId string) (*iaas.Request, error)
GetNetworkExecute(ctx context.Context, projectId, networkId string) (*iaas.Network, error)
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)
}

// CreateNetworkAreaWaitHandler will wait for network area creation
Expand Down Expand Up @@ -143,3 +161,258 @@ func DeleteNetworkWaitHandler(ctx context.Context, a APIClientInterface, project
handler.SetTimeout(10 * time.Minute)
return handler
}

// CreateVolumeWaitHandler will wait for volume creation
func CreateVolumeWaitHandler(ctx context.Context, a APIClientInterface, projectId, volumeId string) *wait.AsyncActionHandler[iaas.Volume] {
handler := wait.New(func() (waitFinished bool, response *iaas.Volume, err error) {
volume, err := a.GetVolumeExecute(ctx, projectId, volumeId)
if err != nil {
return false, volume, err
}
if volume.Id == nil || volume.Status == nil {
return false, volume, fmt.Errorf("create failed for volume with id %s, the response is not valid: the id or the status are missing", volumeId)
}
if *volume.Id == volumeId && *volume.Status == VolumeAvailableStatus {
return true, volume, nil
}
if *volume.Id == volumeId && *volume.Status == ErrorStatus {
return true, volume, fmt.Errorf("create failed for volume with id %s", volumeId)
}
return false, volume, nil
})
handler.SetTimeout(10 * time.Minute)
return handler
}

// DeleteVolumeWaitHandler will wait for volume deletion
func DeleteVolumeWaitHandler(ctx context.Context, a APIClientInterface, projectId, volumeId string) *wait.AsyncActionHandler[iaas.Volume] {
handler := wait.New(func() (waitFinished bool, response *iaas.Volume, err error) {
volume, err := a.GetVolumeExecute(ctx, projectId, volumeId)
if err == nil {
if volume != nil {
if volume.Id == nil || volume.Status == nil {
return false, volume, fmt.Errorf("delete failed for volume with id %s, the response is not valid: the id or the status are missing", volumeId)
}
if *volume.Id == volumeId && *volume.Status == DeleteSuccess {
return true, volume, 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, volume, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err)
}
if oapiErr.StatusCode != http.StatusNotFound {
return false, volume, err
}
return true, nil, nil
})
handler.SetTimeout(10 * time.Minute)
return handler
}

// CreateServerWaitHandler will wait for server creation
func CreateServerWaitHandler(ctx context.Context, a APIClientInterface, projectId, serverId string) *wait.AsyncActionHandler[iaas.Server] {
handler := wait.New(func() (waitFinished bool, response *iaas.Server, err error) {
server, err := a.GetServerExecute(ctx, projectId, serverId)
if err != nil {
return false, server, err
}
if server.Id == nil || server.Status == nil {
return false, server, fmt.Errorf("create failed for server with id %s, the response is not valid: the id or the status are missing", serverId)
}
if *server.Id == serverId && *server.Status == ServerActiveStatus {
return true, server, nil
}
if *server.Id == serverId && *server.Status == ErrorStatus {
if server.ErrorMessage != nil {
return true, server, fmt.Errorf("create failed for server with id %s: %s", serverId, *server.ErrorMessage)
}
return true, server, fmt.Errorf("create failed for server with id %s", serverId)
}
return false, server, nil
})
handler.SetTimeout(20 * time.Minute)
return handler
}

// ResizeServerWaitHandler will wait for server resize
// It checks for an intermediate resizing status and only then waits for the server to become active
func ResizeServerWaitHandler(ctx context.Context, a APIClientInterface, projectId, serverId string) (h *wait.AsyncActionHandler[iaas.Server]) {
handler := wait.New(func() (waitFinished bool, response *iaas.Server, err error) {
server, err := a.GetServerExecute(ctx, projectId, serverId)
if err != nil {
return false, server, err
}

if server.Id == nil || server.Status == nil {
return false, server, fmt.Errorf("resizing failed for server with id %s, the response is not valid: the id or the status are missing", serverId)
}

if *server.Id == serverId && *server.Status == ErrorStatus {
if server.ErrorMessage != nil {
return true, server, fmt.Errorf("resizing failed for server with id %s: %s", serverId, *server.ErrorMessage)
}
return true, server, fmt.Errorf("resizing failed for server with id %s", serverId)
}

if !h.IntermediateStateReached {
if *server.Id == serverId && *server.Status == ServerResizingStatus {
h.IntermediateStateReached = true
return false, server, nil
}
return false, server, nil
}

if *server.Id == serverId && *server.Status == ServerActiveStatus {
return true, server, nil
}

return false, server, nil
})
handler.SetTimeout(20 * time.Minute)
return handler
}

// DeleteServerWaitHandler will wait for volume deletion
func DeleteServerWaitHandler(ctx context.Context, a APIClientInterface, projectId, serverId string) *wait.AsyncActionHandler[iaas.Server] {
handler := wait.New(func() (waitFinished bool, response *iaas.Server, err error) {
server, err := a.GetServerExecute(ctx, projectId, serverId)
if err == nil {
if server != nil {
if server.Id == nil || server.Status == nil {
return false, server, fmt.Errorf("delete failed for server with id %s, the response is not valid: the id or the status are missing", serverId)
}
if *server.Id == serverId && *server.Status == DeleteSuccess {
return true, server, 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, server, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err)
}
if oapiErr.StatusCode != http.StatusNotFound {
return false, server, err
}
return true, nil, nil
})
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 this response header, use the "runtime.WithCaptureHTTPResponse" method from the "core" packaghe to get the raw HTTP response of an SDK operation.
// Then, the value of the request ID can be obtained by accessing the header key which is defined in the constant "XRequestIDHeader" of this package.
//
// Example usage:
//
// var httpResp *http.Response
// ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(context.Background(), &httpResp)
//
// err = iaasClient.AddPublicIpToServer(ctxWithHTTPResp, projectId, serverId, publicIpId).Execute()
//
// requestId := httpResp.Header[wait.XRequestIDHeader][0]
// _, err = wait.ProjectRequestWaitHandler(context.Background(), iaasClient, projectId, requestId).WaitWithContext(context.Background())
func ProjectRequestWaitHandler(ctx context.Context, a APIClientInterface, projectId, requestId string) *wait.AsyncActionHandler[iaas.Request] {
handler := wait.New(func() (waitFinished bool, response *iaas.Request, err error) {
request, err := a.GetProjectRequestExecute(ctx, projectId, requestId)
if err != nil {
return false, request, err
}

if request == nil {
return false, nil, 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
}

// AddVolumeToServerWaitHandler will wait for a volume to be attached to a server
func AddVolumeToServerWaitHandler(ctx context.Context, a APIClientInterface, projectId, serverId, volumeId string) *wait.AsyncActionHandler[iaas.VolumeAttachment] {
handler := wait.New(func() (waitFinished bool, response *iaas.VolumeAttachment, err error) {
volumeAttachment, err := a.GetAttachedVolumeExecute(ctx, projectId, serverId, volumeId)
if err == nil {
if volumeAttachment != nil {
if volumeAttachment.VolumeId == nil {
return false, volumeAttachment, fmt.Errorf("attachment failed for server with id %s and volume with id %s, the response is not valid: the volume id is missing", serverId, volumeId)
}
if *volumeAttachment.VolumeId == volumeId {
return true, volumeAttachment, 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, volumeAttachment, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err)
}
if oapiErr.StatusCode != http.StatusNotFound {
return false, volumeAttachment, err
}
return false, nil, nil
})
handler.SetTimeout(10 * time.Minute)
return handler
}

// RemoveVolumeFromServerWaitHandler will wait for a volume to be attached to a server
func RemoveVolumeFromServerWaitHandler(ctx context.Context, a APIClientInterface, projectId, serverId, volumeId string) *wait.AsyncActionHandler[iaas.VolumeAttachment] {
handler := wait.New(func() (waitFinished bool, response *iaas.VolumeAttachment, err error) {
volumeAttachment, err := a.GetAttachedVolumeExecute(ctx, projectId, serverId, volumeId)
if err == nil {
if volumeAttachment != nil {
if volumeAttachment.VolumeId == nil {
return false, volumeAttachment, fmt.Errorf("remove volume failed for server with id %s and volume with id %s, the response is not valid: the volume id is missing", serverId, volumeId)
}
}
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, volumeAttachment, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err)
}
if oapiErr.StatusCode != http.StatusNotFound {
return false, volumeAttachment, err
}
return true, nil, nil
})
handler.SetTimeout(10 * time.Minute)
return handler
}
Loading
Loading