Skip to content

Commit a58604f

Browse files
Migrate iaasalpha to iaas: add waiters (#937)
* Add waiters, update changelog * Update date
1 parent a992e8f commit a58604f

File tree

4 files changed

+902
-3
lines changed

4 files changed

+902
-3
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## Release (2024-10-21)
2+
3+
- `iaas`: [v0.14.0](services/iaas/CHANGELOG.md#v0140-2024-10-18)
4+
- **Feature:** Add waiter methods for `Volume`, `Server` and `AttachedVolume`
5+
16
## Release (2024-10-18)
27

38
- `iaas`: [v0.13.0](services/iaas/CHANGELOG.md#v0130-2024-10-18)

services/iaas/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## v0.14.0 (2024-10-18)
2+
3+
- **Feature:** Add waiter methods for `Volume`, `Server` and `AttachedVolume`
4+
15
## v0.13.0 (2024-10-18)
26

37
- **Feature:** Add support for managing following resources

services/iaas/wait/wait.go

Lines changed: 274 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,32 @@ import (
1212
)
1313

1414
const (
15-
CreateSuccess = "CREATED"
15+
CreateSuccess = "CREATED"
16+
VolumeAvailableStatus = "AVAILABLE"
17+
DeleteSuccess = "DELETED"
18+
ErrorStatus = "ERROR"
19+
ServerActiveStatus = "ACTIVE"
20+
ServerResizingStatus = "RESIZING"
21+
22+
RequestCreateAction = "CREATE"
23+
RequestUpdateAction = "UPDATE"
24+
RequestDeleteAction = "DELETE"
25+
RequestCreatedStatus = "CREATED"
26+
RequestUpdatedStatus = "UPDATED"
27+
RequestDeletedStatus = "DELETED"
28+
RequestFailedStatus = "FAILED"
29+
30+
XRequestIDHeader = "X-Request-Id"
1631
)
1732

1833
// Interfaces needed for tests
1934
type APIClientInterface interface {
2035
GetNetworkAreaExecute(ctx context.Context, organizationId, areaId string) (*iaas.NetworkArea, error)
2136
GetProjectRequestExecute(ctx context.Context, projectId string, requestId string) (*iaas.Request, error)
2237
GetNetworkExecute(ctx context.Context, projectId, networkId string) (*iaas.Network, error)
38+
GetVolumeExecute(ctx context.Context, projectId string, volumeId string) (*iaas.Volume, error)
39+
GetServerExecute(ctx context.Context, projectId string, serverId string) (*iaas.Server, error)
40+
GetAttachedVolumeExecute(ctx context.Context, projectId string, serverId string, volumeId string) (*iaas.VolumeAttachment, error)
2341
}
2442

2543
// CreateNetworkAreaWaitHandler will wait for network area creation
@@ -143,3 +161,258 @@ func DeleteNetworkWaitHandler(ctx context.Context, a APIClientInterface, project
143161
handler.SetTimeout(10 * time.Minute)
144162
return handler
145163
}
164+
165+
// CreateVolumeWaitHandler will wait for volume creation
166+
func CreateVolumeWaitHandler(ctx context.Context, a APIClientInterface, projectId, volumeId string) *wait.AsyncActionHandler[iaas.Volume] {
167+
handler := wait.New(func() (waitFinished bool, response *iaas.Volume, err error) {
168+
volume, err := a.GetVolumeExecute(ctx, projectId, volumeId)
169+
if err != nil {
170+
return false, volume, err
171+
}
172+
if volume.Id == nil || volume.Status == nil {
173+
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)
174+
}
175+
if *volume.Id == volumeId && *volume.Status == VolumeAvailableStatus {
176+
return true, volume, nil
177+
}
178+
if *volume.Id == volumeId && *volume.Status == ErrorStatus {
179+
return true, volume, fmt.Errorf("create failed for volume with id %s", volumeId)
180+
}
181+
return false, volume, nil
182+
})
183+
handler.SetTimeout(10 * time.Minute)
184+
return handler
185+
}
186+
187+
// DeleteVolumeWaitHandler will wait for volume deletion
188+
func DeleteVolumeWaitHandler(ctx context.Context, a APIClientInterface, projectId, volumeId string) *wait.AsyncActionHandler[iaas.Volume] {
189+
handler := wait.New(func() (waitFinished bool, response *iaas.Volume, err error) {
190+
volume, err := a.GetVolumeExecute(ctx, projectId, volumeId)
191+
if err == nil {
192+
if volume != nil {
193+
if volume.Id == nil || volume.Status == nil {
194+
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)
195+
}
196+
if *volume.Id == volumeId && *volume.Status == DeleteSuccess {
197+
return true, volume, nil
198+
}
199+
}
200+
return false, nil, nil
201+
}
202+
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
203+
if !ok {
204+
return false, volume, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err)
205+
}
206+
if oapiErr.StatusCode != http.StatusNotFound {
207+
return false, volume, err
208+
}
209+
return true, nil, nil
210+
})
211+
handler.SetTimeout(10 * time.Minute)
212+
return handler
213+
}
214+
215+
// CreateServerWaitHandler will wait for server creation
216+
func CreateServerWaitHandler(ctx context.Context, a APIClientInterface, projectId, serverId string) *wait.AsyncActionHandler[iaas.Server] {
217+
handler := wait.New(func() (waitFinished bool, response *iaas.Server, err error) {
218+
server, err := a.GetServerExecute(ctx, projectId, serverId)
219+
if err != nil {
220+
return false, server, err
221+
}
222+
if server.Id == nil || server.Status == nil {
223+
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)
224+
}
225+
if *server.Id == serverId && *server.Status == ServerActiveStatus {
226+
return true, server, nil
227+
}
228+
if *server.Id == serverId && *server.Status == ErrorStatus {
229+
if server.ErrorMessage != nil {
230+
return true, server, fmt.Errorf("create failed for server with id %s: %s", serverId, *server.ErrorMessage)
231+
}
232+
return true, server, fmt.Errorf("create failed for server with id %s", serverId)
233+
}
234+
return false, server, nil
235+
})
236+
handler.SetTimeout(20 * time.Minute)
237+
return handler
238+
}
239+
240+
// ResizeServerWaitHandler will wait for server resize
241+
// It checks for an intermediate resizing status and only then waits for the server to become active
242+
func ResizeServerWaitHandler(ctx context.Context, a APIClientInterface, projectId, serverId string) (h *wait.AsyncActionHandler[iaas.Server]) {
243+
handler := wait.New(func() (waitFinished bool, response *iaas.Server, err error) {
244+
server, err := a.GetServerExecute(ctx, projectId, serverId)
245+
if err != nil {
246+
return false, server, err
247+
}
248+
249+
if server.Id == nil || server.Status == nil {
250+
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)
251+
}
252+
253+
if *server.Id == serverId && *server.Status == ErrorStatus {
254+
if server.ErrorMessage != nil {
255+
return true, server, fmt.Errorf("resizing failed for server with id %s: %s", serverId, *server.ErrorMessage)
256+
}
257+
return true, server, fmt.Errorf("resizing failed for server with id %s", serverId)
258+
}
259+
260+
if !h.IntermediateStateReached {
261+
if *server.Id == serverId && *server.Status == ServerResizingStatus {
262+
h.IntermediateStateReached = true
263+
return false, server, nil
264+
}
265+
return false, server, nil
266+
}
267+
268+
if *server.Id == serverId && *server.Status == ServerActiveStatus {
269+
return true, server, nil
270+
}
271+
272+
return false, server, nil
273+
})
274+
handler.SetTimeout(20 * time.Minute)
275+
return handler
276+
}
277+
278+
// DeleteServerWaitHandler will wait for volume deletion
279+
func DeleteServerWaitHandler(ctx context.Context, a APIClientInterface, projectId, serverId string) *wait.AsyncActionHandler[iaas.Server] {
280+
handler := wait.New(func() (waitFinished bool, response *iaas.Server, err error) {
281+
server, err := a.GetServerExecute(ctx, projectId, serverId)
282+
if err == nil {
283+
if server != nil {
284+
if server.Id == nil || server.Status == nil {
285+
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)
286+
}
287+
if *server.Id == serverId && *server.Status == DeleteSuccess {
288+
return true, server, nil
289+
}
290+
}
291+
return false, nil, nil
292+
}
293+
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
294+
if !ok {
295+
return false, server, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err)
296+
}
297+
if oapiErr.StatusCode != http.StatusNotFound {
298+
return false, server, err
299+
}
300+
return true, nil, nil
301+
})
302+
handler.SetTimeout(20 * time.Minute)
303+
return handler
304+
}
305+
306+
// ProjectRequestWaitHandler will wait for a request to succeed.
307+
//
308+
// 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.
309+
// To get this response header, use the "runtime.WithCaptureHTTPResponse" method from the "core" packaghe to get the raw HTTP response of an SDK operation.
310+
// 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.
311+
//
312+
// Example usage:
313+
//
314+
// var httpResp *http.Response
315+
// ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(context.Background(), &httpResp)
316+
//
317+
// err = iaasClient.AddPublicIpToServer(ctxWithHTTPResp, projectId, serverId, publicIpId).Execute()
318+
//
319+
// requestId := httpResp.Header[wait.XRequestIDHeader][0]
320+
// _, err = wait.ProjectRequestWaitHandler(context.Background(), iaasClient, projectId, requestId).WaitWithContext(context.Background())
321+
func ProjectRequestWaitHandler(ctx context.Context, a APIClientInterface, projectId, requestId string) *wait.AsyncActionHandler[iaas.Request] {
322+
handler := wait.New(func() (waitFinished bool, response *iaas.Request, err error) {
323+
request, err := a.GetProjectRequestExecute(ctx, projectId, requestId)
324+
if err != nil {
325+
return false, request, err
326+
}
327+
328+
if request == nil {
329+
return false, nil, fmt.Errorf("request failed for request with id %s: nil response from GetProjectRequestExecute", requestId)
330+
}
331+
332+
if request.RequestId == nil || request.RequestAction == nil || request.Status == nil {
333+
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)
334+
}
335+
336+
if *request.RequestId != requestId {
337+
return false, request, fmt.Errorf("request failed for request with id %s: the response id doesn't match the request id", requestId)
338+
}
339+
340+
switch *request.RequestAction {
341+
case RequestCreateAction:
342+
if *request.Status == RequestCreatedStatus {
343+
return true, request, nil
344+
}
345+
case RequestUpdateAction:
346+
if *request.Status == RequestUpdatedStatus {
347+
return true, request, nil
348+
}
349+
case RequestDeleteAction:
350+
if *request.Status == RequestDeletedStatus {
351+
return true, request, nil
352+
}
353+
default:
354+
return false, request, fmt.Errorf("request failed for request with id %s, the request action %s is not supported", requestId, *request.RequestAction)
355+
}
356+
357+
if *request.Status == RequestFailedStatus {
358+
return true, request, fmt.Errorf("request failed for request with id %s", requestId)
359+
}
360+
361+
return false, request, nil
362+
})
363+
handler.SetTimeout(20 * time.Minute)
364+
return handler
365+
}
366+
367+
// AddVolumeToServerWaitHandler will wait for a volume to be attached to a server
368+
func AddVolumeToServerWaitHandler(ctx context.Context, a APIClientInterface, projectId, serverId, volumeId string) *wait.AsyncActionHandler[iaas.VolumeAttachment] {
369+
handler := wait.New(func() (waitFinished bool, response *iaas.VolumeAttachment, err error) {
370+
volumeAttachment, err := a.GetAttachedVolumeExecute(ctx, projectId, serverId, volumeId)
371+
if err == nil {
372+
if volumeAttachment != nil {
373+
if volumeAttachment.VolumeId == nil {
374+
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)
375+
}
376+
if *volumeAttachment.VolumeId == volumeId {
377+
return true, volumeAttachment, nil
378+
}
379+
}
380+
return false, nil, nil
381+
}
382+
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
383+
if !ok {
384+
return false, volumeAttachment, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err)
385+
}
386+
if oapiErr.StatusCode != http.StatusNotFound {
387+
return false, volumeAttachment, err
388+
}
389+
return false, nil, nil
390+
})
391+
handler.SetTimeout(10 * time.Minute)
392+
return handler
393+
}
394+
395+
// RemoveVolumeFromServerWaitHandler will wait for a volume to be attached to a server
396+
func RemoveVolumeFromServerWaitHandler(ctx context.Context, a APIClientInterface, projectId, serverId, volumeId string) *wait.AsyncActionHandler[iaas.VolumeAttachment] {
397+
handler := wait.New(func() (waitFinished bool, response *iaas.VolumeAttachment, err error) {
398+
volumeAttachment, err := a.GetAttachedVolumeExecute(ctx, projectId, serverId, volumeId)
399+
if err == nil {
400+
if volumeAttachment != nil {
401+
if volumeAttachment.VolumeId == nil {
402+
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)
403+
}
404+
}
405+
return false, nil, nil
406+
}
407+
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
408+
if !ok {
409+
return false, volumeAttachment, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err)
410+
}
411+
if oapiErr.StatusCode != http.StatusNotFound {
412+
return false, volumeAttachment, err
413+
}
414+
return true, nil, nil
415+
})
416+
handler.SetTimeout(10 * time.Minute)
417+
return handler
418+
}

0 commit comments

Comments
 (0)