From 3e1bfabd72815728f1b131accb4c9b519b88b2d1 Mon Sep 17 00:00:00 2001 From: Volodymyr Boiko Date: Wed, 29 Oct 2025 21:24:39 +0200 Subject: [PATCH] callback for async task waiter --- .../cmd/generate-untyped-resources/main.go | 3 +- codegen/templates/extramethods-only.tpl | 3 +- codegen/templates/resource.tpl | 15 +- core/async_result.go | 343 +++++++++ core/async_result_test.go | 718 +++++++++++++++--- core/interfaces.go | 4 +- core/serde.go | 94 --- core/version | 2 +- resources/typed/blockhost_autogen.go | 6 +- resources/typed/blockhostmapping_autogen.go | 3 +- resources/typed/carrier_autogen.go | 6 +- resources/typed/cbox_autogen.go | 3 +- resources/typed/cluster_autogen.go | 36 +- resources/typed/cnode_autogen.go | 9 +- resources/typed/dbox_autogen.go | 9 +- resources/typed/dnode_autogen.go | 3 +- resources/typed/ebox_autogen.go | 6 +- resources/typed/eventdefinition_autogen.go | 3 +- .../typed/globalsnapshotstream_autogen.go | 6 +- resources/typed/host_autogen.go | 3 +- resources/typed/nvram_autogen.go | 6 +- resources/typed/protectedpath_autogen.go | 3 +- resources/typed/ssd_autogen.go | 6 +- resources/typed/supportbundles_autogen.go | 3 +- resources/typed/tenant_autogen.go | 3 +- resources/typed/user_autogen.go | 3 +- resources/typed/view_autogen.go | 3 +- resources/typed/vms_autogen.go | 6 +- resources/typed/volume_autogen.go | 6 +- resources/untyped/block_host_autogen.go | 6 +- .../untyped/block_host_mapping_autogen.go | 3 +- resources/untyped/carrier_autogen.go | 6 +- resources/untyped/cbox_autogen.go | 3 +- resources/untyped/cluster_autogen.go | 36 +- resources/untyped/cnode_autogen.go | 6 +- resources/untyped/dbox_autogen.go | 6 +- resources/untyped/dnode_autogen.go | 3 +- resources/untyped/ebox_autogen.go | 3 +- resources/untyped/event_definition_autogen.go | 3 +- .../untyped/global_snapshot_stream_autogen.go | 3 +- resources/untyped/host_autogen.go | 3 +- resources/untyped/nvram_autogen.go | 6 +- resources/untyped/ssd_autogen.go | 6 +- resources/untyped/support_bundles_autogen.go | 3 +- resources/untyped/user_autogen.go | 3 +- resources/untyped/v_task.go | 135 ++-- resources/untyped/view_autogen.go | 3 +- resources/untyped/vms_autogen.go | 6 +- resources/untyped/volume_autogen.go | 6 +- 49 files changed, 1112 insertions(+), 451 deletions(-) create mode 100644 core/async_result.go diff --git a/codegen/cmd/generate-untyped-resources/main.go b/codegen/cmd/generate-untyped-resources/main.go index f855fd9..fae003a 100644 --- a/codegen/cmd/generate-untyped-resources/main.go +++ b/codegen/cmd/generate-untyped-resources/main.go @@ -481,8 +481,7 @@ func ({{$.ReceiverName}} *{{$.Name}}) {{.Name}}WithContext_{{.HTTPMethod}}(ctx c return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, {{$.ReceiverName}}.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, {{$.ReceiverName}}.Rest, waitTimeout) {{else}}{{if .ReturnsNoContent}}_, err := core.Request[core.Record](ctx, {{$.ReceiverName}}, http.{{.GoHTTPMethod}}, resourcePath, {{if .HasParams}}params{{else}}nil{{end}}, {{if or .HasBody .SimplifiedBody}}body{{else}}nil{{end}}) return err {{else}}{{if .ReturnsArray}}result, err := core.Request[core.RecordSet](ctx, {{$.ReceiverName}}, http.{{.GoHTTPMethod}}, resourcePath, {{if .HasParams}}params{{else}}nil{{end}}, {{if or .HasBody .SimplifiedBody}}body{{else}}nil{{end}}) diff --git a/codegen/templates/extramethods-only.tpl b/codegen/templates/extramethods-only.tpl index 46ed527..92fcc98 100644 --- a/codegen/templates/extramethods-only.tpl +++ b/codegen/templates/extramethods-only.tpl @@ -99,8 +99,7 @@ func (r *{{$.Name}}) {{.Name}}WithContext_{{.HTTPMethod}}(ctx context.Context{{i return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) {{else}}{{if .ReturnsNoContent}}{{if and (or (and .HasParams .BodyFields) (and .HasBody .BodyFields)) (not .SimplifiedBody)}}_, err = core.Request[core.Record](ctx, r.Untyped.GetResourceMap()[r.GetResourceType()], http.{{.GoHTTPMethod}}, resourcePath, reqParams, reqBody) return err{{else}}_, err := core.Request[core.Record](ctx, r.Untyped.GetResourceMap()[r.GetResourceType()], http.{{.GoHTTPMethod}}, resourcePath, reqParams, reqBody) return err{{end}} diff --git a/codegen/templates/resource.tpl b/codegen/templates/resource.tpl index 13efc9b..7cd7a76 100644 --- a/codegen/templates/resource.tpl +++ b/codegen/templates/resource.tpl @@ -276,8 +276,7 @@ func (r *{{.Name}}) UpdateWithContext(ctx context.Context, id any{{range .Update return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) } {{else}} // Update updates an existing {{.LowerName}} and returns an async task{{if .UpdateSummary}} @@ -305,8 +304,7 @@ func (r *{{.Name}}) UpdateWithContext(ctx context.Context, id any, req *{{.Name} return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) } {{end}} {{else}} @@ -422,8 +420,7 @@ func (r *{{.Name}}) DeleteWithContext(ctx context.Context, req *{{.Name}}SearchP return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) } {{else}} // Delete deletes a {{.LowerName}} with search parameters{{if or .DeleteQueryParams .DeleteBodyParams}} @@ -493,8 +490,7 @@ func (r *{{.Name}}) DeleteByIdWithContext(ctx context.Context, id any{{range .De return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) } {{else}} // DeleteById deletes a {{.LowerName}} by ID{{if .DeleteSummary}} @@ -675,8 +671,7 @@ func (r *{{$.Name}}) {{.Name}}WithContext_{{.HTTPMethod}}(ctx context.Context{{i return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) {{else}}{{if .ReturnsNoContent}}{{if and (or (and .HasParams .BodyFields) (and .HasBody .BodyFields)) (not .SimplifiedBody)}}_, err = core.Request[core.Record](ctx, r.Untyped.GetResourceMap()[r.GetResourceType()], http.{{.GoHTTPMethod}}, resourcePath, reqParams, reqBody) return err{{else}}_, err := core.Request[core.Record](ctx, r.Untyped.GetResourceMap()[r.GetResourceType()], http.{{.GoHTTPMethod}}, resourcePath, reqParams, reqBody) return err{{end}} diff --git a/core/async_result.go b/core/async_result.go new file mode 100644 index 0000000..97fc735 --- /dev/null +++ b/core/async_result.go @@ -0,0 +1,343 @@ +package core + +import ( + "context" + "fmt" + "strings" + "time" +) + +const VTaskKey = "VTask" + +// AsyncResult represents the result of an asynchronous task. +// It contains the task's ID, completion status, and any error that occurred. +// +// Fields: +// - TaskId: The unique identifier of the task +// - Rest: The REST client used to query task status +// - Ctx: The context associated with the task operation +// - Success: True if task completed successfully, false if failed +// - Err: The error that occurred during task execution (nil if successful) +type AsyncResult struct { + TaskId int64 + Rest VastRest + Ctx context.Context + Success bool + Err error +} + +// IsFailed returns true if the task failed during execution. +func (ar *AsyncResult) IsFailed() bool { + return !ar.Success +} + +// IsSuccess returns true if the task completed successfully. +func (ar *AsyncResult) IsSuccess() bool { + return ar.Success +} + +// NewAsyncResult creates a new AsyncResult from a task ID and REST client. +// +// This constructor is used to create an AsyncResult when you already have a task ID. +// The context is stored for potential future use with waiting operations. +// +// Parameters: +// - ctx: The context associated with the task operation +// - taskId: The ID of the asynchronous task +// - rest: The REST client that can be used to query task status +// +// Returns: +// - *AsyncResult: A new AsyncResult instance +func NewAsyncResult(ctx context.Context, taskId int64, rest VastRest) *AsyncResult { + return &AsyncResult{ + Ctx: ctx, + TaskId: taskId, + Rest: rest, + } +} + +// Wait polls the task status until it completes, fails, or times out. +// +// This method continuously polls the VTask resource to check the task's state. +// It uses exponential backoff with configurable intervals to avoid overwhelming the API. +// The Success and Err fields are updated based on the task outcome. +// +// Parameters: +// - timeout: Maximum duration to wait for task completion (uses ar.Ctx for cancellation) +// +// Returns: +// - Record: The final task record when completed +// - error: An error if the task fails, times out, or an API error occurs +// +// Task states handled: +// - "completed": Sets Success=true, returns the task record +// - "running": Continues polling with backoff +// - Any other state: Sets Success=false, Err=error, returns error with task messages +// +// After calling Wait(), check ar.Success and ar.Err for task execution results. +func (ar *AsyncResult) Wait(timeout time.Duration) (Record, error) { + searchParams := Params{"id": ar.TaskId} + waitAPIConditionConfig := &WaitAPIConditionConfig{ + Timeout: timeout, + } + + record, err := WaitAPICondition( + ar.Ctx, + ar.Rest.GetResourceMap()[VTaskKey], + searchParams, + waitAPIConditionConfig, + func(record Record) (bool, error) { + state := strings.ToLower(fmt.Sprintf("%v", record["state"])) + switch state { + case "completed": + return true, nil + case "running": + // Continue polling with backoff + return false, nil + default: + // Task failed or in unexpected state + rawMessages := record["messages"] + messages, ok := rawMessages.([]any) + if !ok || len(messages) == 0 { + taskErr := fmt.Errorf("task %s failed with ID %d: state=%s, no messages or unexpected format", + record.RecordName(), record.RecordID(), state) + return false, taskErr + } + lastMsg := fmt.Sprintf("%v", messages[len(messages)-1]) + taskErr := fmt.Errorf("task %s failed with ID %d: state=%s, message: %s", + record.RecordName(), record.RecordID(), state, lastMsg) + return false, taskErr + } + }, + ) + + // Update AsyncResult fields based on outcome + if err != nil { + ar.Success = false + ar.Err = err + } else { + ar.Success = true + ar.Err = nil + } + + return record, err +} + +// MaybeAsyncResultFromRecord attempts to extract an async task ID from a record and create an AsyncResult. +// +// This function handles two common patterns in VAST API responses: +// 1. Direct task response: The record itself has a ResourceTypeKey and represents the task +// 2. Nested task response: The record has an "async_task" field containing the task information +// +// If the record doesn't contain any task information, or if the task ID cannot be extracted, +// this function returns nil. +// +// Parameters: +// - ctx: The context to associate with the async result +// - record: The record that may contain async task information +// - rest: The REST client for task operations +// +// Returns: +// - *AsyncResult: An AsyncResult if task information was found, nil otherwise +func MaybeAsyncResultFromRecord(ctx context.Context, record Record, rest VastRest) *AsyncResult { + var ( + taskId int64 + asyncResult *AsyncResult + ) + + if record.Empty() { + return nil + } + + // Check if the record itself is a task (has ResourceTypeKey) + if resourceType, ok := record[ResourceTypeKey]; ok { + if resourceType != VTaskKey { + return nil + } + + // Only call RecordID if "id" field exists to avoid panic + if _, hasId := record["id"]; hasId { + taskId = record.RecordID() + } + } else { + // Check for nested async_task field + if asyncTask, ok := record["async_task"]; ok { + var m map[string]any + if m, ok = asyncTask.(map[string]any); ok { + if _, hasId := m["id"]; hasId { + taskId = ToRecord(m).RecordID() + } + } + } + } + + if taskId != 0 { + asyncResult = NewAsyncResult(ctx, taskId, rest) + } + + return asyncResult + +} + +// WaitAPIConditionConfig defines retry/backoff parameters for polling operations. +// +// This configuration controls how WaitAPICondition polls an API endpoint, +// including timeout, polling intervals, and exponential backoff behavior. +// +// Fields: +// - Timeout: Maximum duration to wait before giving up (default: 10 minutes) +// - Interval: Initial polling interval between API calls (default: 500ms) +// - MaxInterval: Maximum polling interval after backoff (default: 30 seconds) +// - BackoffFactor: Multiplier for exponential backoff (default: 0.25 = 25% increase per iteration) +// +// Example with custom configuration: +// +// cfg := &WaitAPIConditionConfig{ +// Timeout: 5 * time.Minute, +// Interval: 1 * time.Second, +// MaxInterval: 10 * time.Second, +// BackoffFactor: 0.5, // 50% increase per iteration +// } +// +// Zero values will be replaced with defaults by the normalize() method. +type WaitAPIConditionConfig struct { + Timeout time.Duration // Maximum total wait time + Interval time.Duration // Current/initial polling interval (mutated by NextInterval) + MaxInterval time.Duration // Cap for exponential backoff + BackoffFactor float64 // Rate of interval increase (0.25 = 25% per iteration) +} + +// normalize fills in missing (zero) values with sensible defaults. +// +// Default values: +// - Timeout: 10 minutes +// - Interval: 500 milliseconds +// - MaxInterval: 30 seconds +// - BackoffFactor: 0.25 (25% increase per iteration) +// +// This method modifies the config in-place. +func (c *WaitAPIConditionConfig) normalize() { + if c.Timeout == 0 { + c.Timeout = 10 * time.Minute + } + if c.Interval == 0 { + c.Interval = 500 * time.Millisecond + } + if c.MaxInterval == 0 { + c.MaxInterval = 30 * time.Second + } + if c.BackoffFactor == 0 { + c.BackoffFactor = 0.25 + } +} + +// NextInterval returns the current interval and updates the internal state for the next iteration. +// +// This method implements exponential backoff by: +// 1. Returning the current interval value (for immediate use) +// 2. Calculating the next interval as: current * (1.0 + BackoffFactor) +// 3. Capping the next interval at MaxInterval +// 4. Updating c.Interval for the next call +// +// Example progression with Interval=500ms, BackoffFactor=0.25, MaxInterval=30s: +// - 1st call: returns 500ms, sets next to 625ms (500 * 1.25) +// - 2nd call: returns 625ms, sets next to 781ms (625 * 1.25) +// - 3rd call: returns 781ms, sets next to 976ms (781 * 1.25) +// - ...continues until reaching MaxInterval (30s) +// +// WARNING: This method mutates the config's Interval field. Do not reuse the same +// config instance for multiple concurrent polling operations. +func (c *WaitAPIConditionConfig) NextInterval() time.Duration { + current := c.Interval + + // Calculate next interval for future calls using exponential backoff + next := time.Duration(float64(c.Interval) * (1.0 + c.BackoffFactor)) + if next > c.MaxInterval { + next = c.MaxInterval + } + c.Interval = next + + return current +} + +// WaitAPICondition polls an API endpoint until a condition is met or timeout occurs. +// +// This is a generic polling function that repeatedly calls an API endpoint, +// applies a verification function to the result, and waits with exponential backoff +// between attempts until the condition is satisfied or a timeout is reached. +// +// Parameters: +// - ctx: The context for the operation (can be used for cancellation) +// - caller: The resource API to poll (must support GetByIdWithContext or GetWithContext) +// - searchParams: Parameters to identify the resource (if contains "id", uses GetById, otherwise Get) +// - waitAPIConditionConfig: Configuration for timeout, intervals, and backoff (nil uses defaults) +// - verifyFn: Function that checks if the condition is met. Returns (true, nil) when complete, +// (false, nil) to continue polling, or (false, error) to abort with error. +// +// Returns: +// - Record: The final record when the condition is met +// - error: An error if verification fails, timeout occurs, or API call fails +// +// Default configuration (when waitAPIConditionConfig is nil): +// - Timeout: 10 minutes +// - Initial Interval: 500ms +// - Max Interval: 30 seconds +// - Backoff Factor: 0.25 (25% increase per iteration) +func WaitAPICondition( + ctx context.Context, + caller VastResourceAPIWithContext, + searchParams Params, + waitAPIConditionConfig *WaitAPIConditionConfig, + verifyFn func(Record) (bool, error), +) (Record, error) { + // Normalize config - use defaults if nil or zero values + if waitAPIConditionConfig == nil { + waitAPIConditionConfig = &WaitAPIConditionConfig{} + } + waitAPIConditionConfig.normalize() + + // Create a timeout context using the configured timeout + timeoutCtx, cancel := context.WithTimeout(ctx, waitAPIConditionConfig.Timeout) + defer cancel() + + // Polling loop with exponential backoff + for { + select { + case <-timeoutCtx.Done(): + // Check if it's a timeout or cancellation + if ctx.Err() != nil { + return nil, fmt.Errorf("WaitAPICondition cancelled: %w", ctx.Err()) + } + return nil, fmt.Errorf("WaitAPICondition timeout after %v", waitAPIConditionConfig.Timeout) + + default: + var ( + record Record + err error + ) + + // Use GetById if "id" parameter is present, otherwise use Get with search params + if id, ok := searchParams["id"]; ok { + record, err = caller.GetByIdWithContext(timeoutCtx, id) + } else { + record, err = caller.GetWithContext(timeoutCtx, searchParams) + } + if err != nil { + return nil, fmt.Errorf("WaitAPICondition API call failed: %w", err) + } + + // Check if condition is met + completed, err := verifyFn(record) + if err != nil { + return nil, fmt.Errorf("WaitAPICondition verification failed: %w", err) + } + if completed { + return record, nil + } + + // Sleep for current interval, then bump interval for next iteration + sleepFor := waitAPIConditionConfig.NextInterval() + time.Sleep(sleepFor) + } + } +} diff --git a/core/async_result_test.go b/core/async_result_test.go index c88a9a1..543c121 100644 --- a/core/async_result_test.go +++ b/core/async_result_test.go @@ -2,13 +2,16 @@ package core import ( "context" + "errors" + "fmt" + "strings" "testing" + "time" ) -// mockVastRest is a mock implementation of VastRest for testing +// Mock implementations for testing type mockVastRest struct { resourceMap map[string]VastResourceAPIWithContext - ctx context.Context } func (m *mockVastRest) GetSession() RESTSession { @@ -20,16 +23,72 @@ func (m *mockVastRest) GetResourceMap() map[string]VastResourceAPIWithContext { } func (m *mockVastRest) GetCtx() context.Context { - if m.ctx == nil { - return context.Background() - } - return m.ctx + return context.Background() +} + +func (m *mockVastRest) SetCtx(ctx context.Context) {} + +type mockVastResourceAPI struct { + getByIdFunc func(ctx context.Context, id any) (Record, error) + getFunc func(ctx context.Context, params Params) (Record, error) } -func (m *mockVastRest) SetCtx(ctx context.Context) { - m.ctx = ctx +func (m *mockVastResourceAPI) Session() RESTSession { return nil } +func (m *mockVastResourceAPI) GetResourceType() string { return VTaskKey } +func (m *mockVastResourceAPI) GetResourcePath() string { return "/vtasks/" } +func (m *mockVastResourceAPI) List(Params) (RecordSet, error) { return nil, nil } +func (m *mockVastResourceAPI) Create(Params) (Record, error) { return nil, nil } +func (m *mockVastResourceAPI) Update(any, Params) (Record, error) { return nil, nil } +func (m *mockVastResourceAPI) Delete(Params, Params) (Record, error) { return nil, nil } +func (m *mockVastResourceAPI) DeleteById(any, Params, Params) (Record, error) { return nil, nil } +func (m *mockVastResourceAPI) Ensure(Params, Params) (Record, error) { return nil, nil } +func (m *mockVastResourceAPI) Get(Params) (Record, error) { return nil, nil } +func (m *mockVastResourceAPI) GetById(any) (Record, error) { return nil, nil } +func (m *mockVastResourceAPI) Exists(Params) (bool, error) { return false, nil } +func (m *mockVastResourceAPI) MustExists(Params) bool { return false } +func (m *mockVastResourceAPI) GetIterator(Params, int) Iterator { return nil } +func (m *mockVastResourceAPI) Lock(...any) func() { return func() {} } + +func (m *mockVastResourceAPI) ListWithContext(context.Context, Params) (RecordSet, error) { + return nil, nil +} +func (m *mockVastResourceAPI) CreateWithContext(context.Context, Params) (Record, error) { + return nil, nil +} +func (m *mockVastResourceAPI) UpdateWithContext(context.Context, any, Params) (Record, error) { + return nil, nil +} +func (m *mockVastResourceAPI) DeleteWithContext(context.Context, Params, Params, Params) (Record, error) { + return nil, nil +} +func (m *mockVastResourceAPI) DeleteByIdWithContext(context.Context, any, Params, Params) (Record, error) { + return nil, nil +} +func (m *mockVastResourceAPI) EnsureWithContext(context.Context, Params, Params) (Record, error) { + return nil, nil +} +func (m *mockVastResourceAPI) GetWithContext(ctx context.Context, params Params) (Record, error) { + if m.getFunc != nil { + return m.getFunc(ctx, params) + } + return nil, nil +} +func (m *mockVastResourceAPI) GetByIdWithContext(ctx context.Context, id any) (Record, error) { + if m.getByIdFunc != nil { + return m.getByIdFunc(ctx, id) + } + return nil, nil +} +func (m *mockVastResourceAPI) ExistsWithContext(context.Context, Params) (bool, error) { + return false, nil +} +func (m *mockVastResourceAPI) MustExistsWithContext(context.Context, Params) bool { return false } +func (m *mockVastResourceAPI) GetIteratorWithContext(context.Context, Params, int) Iterator { + return nil } +// Tests for AsyncResult + func TestNewAsyncResult(t *testing.T) { ctx := context.Background() taskId := int64(12345) @@ -37,196 +96,679 @@ func TestNewAsyncResult(t *testing.T) { result := NewAsyncResult(ctx, taskId, rest) - if result == nil { - t.Fatal("NewAsyncResult returned nil") - } - if result.TaskId != taskId { t.Errorf("Expected TaskId %d, got %d", taskId, result.TaskId) } - + if result.Ctx != ctx { + t.Error("Context not set correctly") + } if result.Rest != rest { - t.Error("Expected Rest to match provided rest client") + t.Error("Rest not set correctly") + } + if result.Success { + t.Error("Success should be false by default") + } + if result.Err != nil { + t.Error("Err should be nil by default") + } +} + +func TestAsyncResult_IsFailed(t *testing.T) { + tests := []struct { + name string + success bool + want bool + }{ + {"Failed task", false, true}, + {"Successful task", true, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ar := &AsyncResult{Success: tt.success} + if got := ar.IsFailed(); got != tt.want { + t.Errorf("IsFailed() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAsyncResult_IsSuccess(t *testing.T) { + tests := []struct { + name string + success bool + want bool + }{ + {"Successful task", true, true}, + {"Failed task", false, false}, } - if result.ctx != ctx { - t.Error("Expected ctx to match provided context") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ar := &AsyncResult{Success: tt.success} + if got := ar.IsSuccess(); got != tt.want { + t.Errorf("IsSuccess() = %v, want %v", got, tt.want) + } + }) } } -func TestMaybeAsyncResultFromRecord_EmptyMap(t *testing.T) { +func TestAsyncResult_Wait_Completed(t *testing.T) { ctx := context.Background() - rest := &mockVastRest{} - record := Record{} + taskId := int64(123) + + mockAPI := &mockVastResourceAPI{ + getByIdFunc: func(ctx context.Context, id any) (Record, error) { + return Record{ + "id": 123, + "state": "completed", + ResourceTypeKey: VTaskKey, + }, nil + }, + } - result := MaybeAsyncResultFromRecord(ctx, record, rest) + rest := &mockVastRest{ + resourceMap: map[string]VastResourceAPIWithContext{ + VTaskKey: mockAPI, + }, + } - if result != nil { - t.Error("Expected nil for empty record, got non-nil") + ar := NewAsyncResult(ctx, taskId, rest) + record, err := ar.Wait(100 * time.Millisecond) + + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if !ar.Success { + t.Error("Expected Success to be true") + } + if ar.Err != nil { + t.Errorf("Expected Err to be nil, got %v", ar.Err) + } + if record["state"] != "completed" { + t.Error("Expected completed state in record") } } -func TestMaybeAsyncResultFromRecord_DirectTaskResponse(t *testing.T) { +func TestAsyncResult_Wait_FailedTask(t *testing.T) { ctx := context.Background() - rest := &mockVastRest{} - taskId := int64(67890) + taskId := int64(123) + + mockAPI := &mockVastResourceAPI{ + getByIdFunc: func(ctx context.Context, id any) (Record, error) { + return Record{ + "id": 123, + "name": "test-task", + "state": "failed", + "messages": []any{"Task execution error"}, + ResourceTypeKey: VTaskKey, + }, nil + }, + } - record := Record{ - ResourceTypeKey: "VTask", - "id": taskId, + rest := &mockVastRest{ + resourceMap: map[string]VastResourceAPIWithContext{ + VTaskKey: mockAPI, + }, } - result := MaybeAsyncResultFromRecord(ctx, record, rest) + ar := NewAsyncResult(ctx, taskId, rest) + _, err := ar.Wait(100 * time.Millisecond) - if result == nil { - t.Fatal("Expected non-nil result for direct task response") + if err == nil { + t.Error("Expected error for failed task") + } + if ar.Success { + t.Error("Expected Success to be false") } + if ar.Err == nil { + t.Error("Expected Err to be set") + } +} - if result.TaskId != taskId { - t.Errorf("Expected TaskId %d, got %d", taskId, result.TaskId) +func TestAsyncResult_Wait_FailedTaskNoMessages(t *testing.T) { + ctx := context.Background() + taskId := int64(123) + + mockAPI := &mockVastResourceAPI{ + getByIdFunc: func(ctx context.Context, id any) (Record, error) { + return Record{ + "id": 123, + "name": "test-task", + "state": "error", + ResourceTypeKey: VTaskKey, + }, nil + }, } - if result.Rest != rest { - t.Error("Expected Rest to match provided rest client") + rest := &mockVastRest{ + resourceMap: map[string]VastResourceAPIWithContext{ + VTaskKey: mockAPI, + }, + } + + ar := NewAsyncResult(ctx, taskId, rest) + _, err := ar.Wait(100 * time.Millisecond) + + if err == nil { + t.Error("Expected error for failed task") + } + if !ar.IsFailed() { + t.Error("Expected task to be failed") + } + if ar.Err == nil { + t.Error("Expected Err to be set") + } + // Check error message contains expected text + if err != nil && !strings.Contains(err.Error(), "no messages or unexpected format") { + t.Errorf("Expected 'no messages' in error, got: %v", err) } } -func TestMaybeAsyncResultFromRecord_NestedTaskResponse(t *testing.T) { +func TestAsyncResult_Wait_RunningThenCompleted(t *testing.T) { ctx := context.Background() - rest := &mockVastRest{} - taskId := int64(99999) + taskId := int64(123) + + callCount := 0 + mockAPI := &mockVastResourceAPI{ + getByIdFunc: func(ctx context.Context, id any) (Record, error) { + callCount++ + if callCount < 3 { + return Record{ + "id": 123, + "state": "running", + ResourceTypeKey: VTaskKey, + }, nil + } + return Record{ + "id": 123, + "state": "completed", + ResourceTypeKey: VTaskKey, + }, nil + }, + } - record := Record{ - "async_task": map[string]any{ - "id": taskId, + rest := &mockVastRest{ + resourceMap: map[string]VastResourceAPIWithContext{ + VTaskKey: mockAPI, }, } - result := MaybeAsyncResultFromRecord(ctx, record, rest) + ar := NewAsyncResult(ctx, taskId, rest) - if result == nil { - t.Fatal("Expected non-nil result for nested task response") + // Use very short timeout and intervals for faster test + config := &WaitAPIConditionConfig{ + Timeout: 5 * time.Second, + Interval: 10 * time.Millisecond, + MaxInterval: 50 * time.Millisecond, + BackoffFactor: 0.25, } - if result.TaskId != taskId { - t.Errorf("Expected TaskId %d, got %d", taskId, result.TaskId) + record, err := WaitAPICondition( + ar.Ctx, + ar.Rest.GetResourceMap()[VTaskKey], + Params{"id": ar.TaskId}, + config, + func(record Record) (bool, error) { + state := fmt.Sprintf("%v", record["state"]) + if state == "completed" { + return true, nil + } + return false, nil + }, + ) + + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if callCount < 3 { + t.Errorf("Expected at least 3 calls, got %d", callCount) } + if record["state"] != "completed" { + t.Error("Expected completed state") + } +} - if result.Rest != rest { - t.Error("Expected Rest to match provided rest client") +// Tests for MaybeAsyncResultFromRecord + +func TestMaybeAsyncResultFromRecord_EmptyRecord(t *testing.T) { + ctx := context.Background() + rest := &mockVastRest{} + + result := MaybeAsyncResultFromRecord(ctx, Record{}, rest) + + if result != nil { + t.Error("Expected nil for empty record") } } -func TestMaybeAsyncResultFromRecord_NestedTaskResponse_FloatId(t *testing.T) { - // JSON unmarshaling often produces float64 for numbers +func TestMaybeAsyncResultFromRecord_DirectTask(t *testing.T) { ctx := context.Background() rest := &mockVastRest{} - taskId := float64(11111) record := Record{ - "async_task": map[string]any{ - "id": taskId, - }, + "id": int64(456), + ResourceTypeKey: VTaskKey, } result := MaybeAsyncResultFromRecord(ctx, record, rest) if result == nil { - t.Fatal("Expected non-nil result for nested task response with float ID") + t.Fatal("Expected non-nil result") } - - expectedTaskId := int64(11111) - if result.TaskId != expectedTaskId { - t.Errorf("Expected TaskId %d, got %d", expectedTaskId, result.TaskId) + if result.TaskId != 456 { + t.Errorf("Expected TaskId 456, got %d", result.TaskId) } } -func TestMaybeAsyncResultFromRecord_NoTaskInfo(t *testing.T) { +func TestMaybeAsyncResultFromRecord_DirectTaskNoID(t *testing.T) { ctx := context.Background() rest := &mockVastRest{} record := Record{ - "name": "test", - "description": "no task here", + ResourceTypeKey: VTaskKey, } result := MaybeAsyncResultFromRecord(ctx, record, rest) if result != nil { - t.Error("Expected nil for record without task information") + t.Error("Expected nil for task without ID") } } -func TestMaybeAsyncResultFromRecord_InvalidAsyncTask(t *testing.T) { +func TestMaybeAsyncResultFromRecord_WrongResourceType(t *testing.T) { ctx := context.Background() rest := &mockVastRest{} - // async_task is not a map record := Record{ - "async_task": "invalid", + "id": int64(456), + ResourceTypeKey: "SomeOtherResource", } result := MaybeAsyncResultFromRecord(ctx, record, rest) if result != nil { - t.Error("Expected nil for record with invalid async_task format") + t.Error("Expected nil for non-task resource") } } -func TestMaybeAsyncResultFromRecord_AsyncTaskWithoutId(t *testing.T) { +func TestMaybeAsyncResultFromRecord_NestedAsyncTask(t *testing.T) { ctx := context.Background() rest := &mockVastRest{} record := Record{ "async_task": map[string]any{ - "status": "running", - // no id field + "id": int64(789), }, } result := MaybeAsyncResultFromRecord(ctx, record, rest) - if result != nil { - t.Error("Expected nil for async_task without id") + if result == nil { + t.Fatal("Expected non-nil result") + } + if result.TaskId != 789 { + t.Errorf("Expected TaskId 789, got %d", result.TaskId) } } -func TestMaybeAsyncResultFromRecord_ZeroTaskId(t *testing.T) { +func TestMaybeAsyncResultFromRecord_NestedAsyncTaskNoID(t *testing.T) { ctx := context.Background() rest := &mockVastRest{} record := Record{ - ResourceTypeKey: "VTask", - "id": int64(0), + "async_task": map[string]any{ + "name": "task", + }, } result := MaybeAsyncResultFromRecord(ctx, record, rest) if result != nil { - t.Error("Expected nil for zero task ID") + t.Error("Expected nil for nested task without ID") } } -func TestMaybeAsyncResultFromRecord_WithContext(t *testing.T) { - type key string - ctxKey := key("test") - ctx := context.WithValue(context.Background(), ctxKey, "test-value") +func TestMaybeAsyncResultFromRecord_InvalidAsyncTaskType(t *testing.T) { + ctx := context.Background() rest := &mockVastRest{} - taskId := int64(12345) record := Record{ - ResourceTypeKey: "VTask", - "id": taskId, + "async_task": "not a map", } result := MaybeAsyncResultFromRecord(ctx, record, rest) - if result == nil { - t.Fatal("Expected non-nil result") + if result != nil { + t.Error("Expected nil for invalid async_task type") + } +} + +// Tests for WaitAPIConditionConfig + +func TestWaitAPIConditionConfig_Normalize_AllDefaults(t *testing.T) { + config := &WaitAPIConditionConfig{} + config.normalize() + + if config.Timeout != 10*time.Minute { + t.Errorf("Expected Timeout 10m, got %v", config.Timeout) + } + if config.Interval != 500*time.Millisecond { + t.Errorf("Expected Interval 500ms, got %v", config.Interval) + } + if config.MaxInterval != 30*time.Second { + t.Errorf("Expected MaxInterval 30s, got %v", config.MaxInterval) + } + if config.BackoffFactor != 0.25 { + t.Errorf("Expected BackoffFactor 0.25, got %v", config.BackoffFactor) + } +} + +func TestWaitAPIConditionConfig_Normalize_PartialDefaults(t *testing.T) { + config := &WaitAPIConditionConfig{ + Timeout: 5 * time.Minute, + Interval: 1 * time.Second, } + config.normalize() - // Verify the context was preserved - if result.ctx.Value(ctxKey) != "test-value" { - t.Error("Expected context to be preserved in AsyncResult") + if config.Timeout != 5*time.Minute { + t.Errorf("Expected Timeout 5m, got %v", config.Timeout) + } + if config.Interval != 1*time.Second { + t.Errorf("Expected Interval 1s, got %v", config.Interval) + } + if config.MaxInterval != 30*time.Second { + t.Errorf("Expected MaxInterval 30s (default), got %v", config.MaxInterval) + } + if config.BackoffFactor != 0.25 { + t.Errorf("Expected BackoffFactor 0.25 (default), got %v", config.BackoffFactor) + } +} + +func TestWaitAPIConditionConfig_NextInterval(t *testing.T) { + config := &WaitAPIConditionConfig{ + Interval: 100 * time.Millisecond, + MaxInterval: 500 * time.Millisecond, + BackoffFactor: 0.5, + } + + // First call: returns 100ms, sets next to 150ms (100 * 1.5) + interval1 := config.NextInterval() + if interval1 != 100*time.Millisecond { + t.Errorf("First interval: expected 100ms, got %v", interval1) + } + if config.Interval != 150*time.Millisecond { + t.Errorf("After first call: expected interval 150ms, got %v", config.Interval) + } + + // Second call: returns 150ms, sets next to 225ms (150 * 1.5) + interval2 := config.NextInterval() + if interval2 != 150*time.Millisecond { + t.Errorf("Second interval: expected 150ms, got %v", interval2) + } + if config.Interval != 225*time.Millisecond { + t.Errorf("After second call: expected interval 225ms, got %v", config.Interval) + } +} + +func TestWaitAPIConditionConfig_NextInterval_CapsAtMax(t *testing.T) { + config := &WaitAPIConditionConfig{ + Interval: 400 * time.Millisecond, + MaxInterval: 500 * time.Millisecond, + BackoffFactor: 0.5, + } + + // First call: returns 400ms, tries to set 600ms but caps at 500ms + interval1 := config.NextInterval() + if interval1 != 400*time.Millisecond { + t.Errorf("Expected 400ms, got %v", interval1) + } + if config.Interval != 500*time.Millisecond { + t.Errorf("Expected interval capped at 500ms, got %v", config.Interval) + } + + // Second call: returns 500ms (at max), stays at 500ms + interval2 := config.NextInterval() + if interval2 != 500*time.Millisecond { + t.Errorf("Expected 500ms, got %v", interval2) + } + if config.Interval != 500*time.Millisecond { + t.Errorf("Expected interval to stay at 500ms, got %v", config.Interval) + } +} + +// Tests for WaitAPICondition + +func TestWaitAPICondition_ImmediateSuccess(t *testing.T) { + ctx := context.Background() + + mockAPI := &mockVastResourceAPI{ + getByIdFunc: func(ctx context.Context, id any) (Record, error) { + return Record{"status": "ready"}, nil + }, + } + + config := &WaitAPIConditionConfig{ + Timeout: 1 * time.Second, + Interval: 100 * time.Millisecond, + } + + record, err := WaitAPICondition( + ctx, + mockAPI, + Params{"id": 1}, + config, + func(r Record) (bool, error) { + return r["status"] == "ready", nil + }, + ) + + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if record["status"] != "ready" { + t.Error("Expected status to be ready") + } +} + +func TestWaitAPICondition_VerificationError(t *testing.T) { + ctx := context.Background() + + mockAPI := &mockVastResourceAPI{ + getByIdFunc: func(ctx context.Context, id any) (Record, error) { + return Record{"status": "error"}, nil + }, + } + + config := &WaitAPIConditionConfig{ + Timeout: 1 * time.Second, + Interval: 100 * time.Millisecond, + } + + expectedErr := errors.New("verification failed") + + _, err := WaitAPICondition( + ctx, + mockAPI, + Params{"id": 1}, + config, + func(r Record) (bool, error) { + return false, expectedErr + }, + ) + + if err == nil { + t.Error("Expected error from verification function") + } + if !strings.Contains(err.Error(), "verification failed") { + t.Errorf("Expected 'verification failed' in error, got: %v", err) + } +} + +func TestWaitAPICondition_Timeout(t *testing.T) { + ctx := context.Background() + + mockAPI := &mockVastResourceAPI{ + getByIdFunc: func(ctx context.Context, id any) (Record, error) { + return Record{"status": "pending"}, nil + }, + } + + config := &WaitAPIConditionConfig{ + Timeout: 200 * time.Millisecond, + Interval: 50 * time.Millisecond, + } + + _, err := WaitAPICondition( + ctx, + mockAPI, + Params{"id": 1}, + config, + func(r Record) (bool, error) { + // Never completes + return false, nil + }, + ) + + if err == nil { + t.Error("Expected timeout error") + } + if !strings.Contains(err.Error(), "timeout") { + t.Errorf("Expected 'timeout' in error, got: %v", err) + } +} + +func TestWaitAPICondition_ContextCancellation(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + + mockAPI := &mockVastResourceAPI{ + getByIdFunc: func(ctx context.Context, id any) (Record, error) { + return Record{"status": "pending"}, nil + }, + } + + config := &WaitAPIConditionConfig{ + Timeout: 5 * time.Second, + Interval: 100 * time.Millisecond, + } + + // Cancel context after a short delay + go func() { + time.Sleep(150 * time.Millisecond) + cancel() + }() + + _, err := WaitAPICondition( + ctx, + mockAPI, + Params{"id": 1}, + config, + func(r Record) (bool, error) { + return false, nil + }, + ) + + if err == nil { + t.Error("Expected cancellation error") + } + if !strings.Contains(err.Error(), "cancelled") { + t.Errorf("Expected 'cancelled' in error, got: %v", err) + } +} + +func TestWaitAPICondition_APIError(t *testing.T) { + ctx := context.Background() + + expectedErr := errors.New("API call failed") + mockAPI := &mockVastResourceAPI{ + getByIdFunc: func(ctx context.Context, id any) (Record, error) { + return nil, expectedErr + }, + } + + config := &WaitAPIConditionConfig{ + Timeout: 1 * time.Second, + Interval: 100 * time.Millisecond, + } + + _, err := WaitAPICondition( + ctx, + mockAPI, + Params{"id": 1}, + config, + func(r Record) (bool, error) { + return true, nil + }, + ) + + if err == nil { + t.Error("Expected API error") + } + if !strings.Contains(err.Error(), "API call failed") { + t.Errorf("Expected 'API call failed' in error, got: %v", err) + } +} + +func TestWaitAPICondition_NilConfig(t *testing.T) { + ctx := context.Background() + + mockAPI := &mockVastResourceAPI{ + getByIdFunc: func(ctx context.Context, id any) (Record, error) { + return Record{"status": "ready"}, nil + }, + } + + record, err := WaitAPICondition( + ctx, + mockAPI, + Params{"id": 1}, + nil, // nil config should use defaults + func(r Record) (bool, error) { + return r["status"] == "ready", nil + }, + ) + + if err != nil { + t.Errorf("Expected no error with nil config, got %v", err) + } + if record["status"] != "ready" { + t.Error("Expected status to be ready") + } +} + +func TestWaitAPICondition_UseGetInsteadOfGetById(t *testing.T) { + ctx := context.Background() + + getCalled := false + mockAPI := &mockVastResourceAPI{ + getFunc: func(ctx context.Context, params Params) (Record, error) { + getCalled = true + return Record{"status": "ready"}, nil + }, + } + + config := &WaitAPIConditionConfig{ + Timeout: 1 * time.Second, + Interval: 100 * time.Millisecond, + } + + record, err := WaitAPICondition( + ctx, + mockAPI, + Params{"name": "test"}, // No "id" param + config, + func(r Record) (bool, error) { + return r["status"] == "ready", nil + }, + ) + + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if !getCalled { + t.Error("Expected Get to be called instead of GetById") + } + if record["status"] != "ready" { + t.Error("Expected status to be ready") } } diff --git a/core/interfaces.go b/core/interfaces.go index 4cee723..3f980f7 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -60,8 +60,8 @@ type Awaitable interface { // It allows the core package to call task waiting functionality without creating a circular dependency // with the untyped package. type TaskWaiter interface { - WaitTaskWithContext(ctx context.Context, taskId int64) (Record, error) - WaitTask(taskId int64, duration time.Duration) (Record, error) + WaitTaskWithContext(ctx context.Context, taskId int64, timeout time.Duration) (Record, error) + WaitTask(taskId int64, timeout time.Duration) (Record, error) } // RequestInterceptor defines a middleware-style interface for intercepting API requests diff --git a/core/serde.go b/core/serde.go index c4f0163..c5bbc88 100644 --- a/core/serde.go +++ b/core/serde.go @@ -2,7 +2,6 @@ package core import ( "bytes" - "context" "encoding/json" "fmt" "io" @@ -744,96 +743,3 @@ func ModelToRecord(model any) Record { return record } - -// ###################################################### -// ASYNC RESULT -// ###################################################### - -// AsyncResult represents the result of an asynchronous task. -// It contains the task's ID and necessary context for waiting on the task to complete. -type AsyncResult struct { - TaskId int64 - Rest VastRest - ctx context.Context - Status string -} - -func (ar AsyncResult) IsFailed() bool { - return strings.ToLower(ar.Status) == "failed" -} - -// NewAsyncResult creates a new AsyncResult from a task ID and REST client. -// -// This constructor is used to create an AsyncResult when you already have a task ID. -// The context is stored for potential future use with waiting operations. -// -// Parameters: -// - ctx: The context associated with the task operation -// - taskId: The ID of the asynchronous task -// - rest: The REST client that can be used to query task status -// -// Returns: -// - *AsyncResult: A new AsyncResult instance -func NewAsyncResult(ctx context.Context, taskId int64, rest VastRest) *AsyncResult { - return &AsyncResult{ - ctx: ctx, - TaskId: taskId, - Rest: rest, - } -} - -// MaybeAsyncResultFromRecord attempts to extract an async task ID from a record and create an AsyncResult. -// -// This function handles two common patterns in VAST API responses: -// 1. Direct task response: The record itself has a ResourceTypeKey and represents the task -// 2. Nested task response: The record has an "async_task" field containing the task information -// -// If the record doesn't contain any task information, or if the task ID cannot be extracted, -// this function returns nil. -// -// Parameters: -// - ctx: The context to associate with the async result -// - record: The record that may contain async task information -// - rest: The REST client for task operations -// -// Returns: -// - *AsyncResult: An AsyncResult if task information was found, nil otherwise -func MaybeAsyncResultFromRecord(ctx context.Context, record Record, rest VastRest) *AsyncResult { - var ( - taskId int64 - asyncResult *AsyncResult - ) - - if record.Empty() { - return nil - } - - // Check if the record itself is a task (has ResourceTypeKey) - if resourceType, ok := record[ResourceTypeKey]; ok { - if resourceType != "VTask" { - return nil - } - - // Only call RecordID if "id" field exists to avoid panic - if _, hasId := record["id"]; hasId { - taskId = record.RecordID() - } - } else { - // Check for nested async_task field - if asyncTask, ok := record["async_task"]; ok { - var m map[string]any - if m, ok = asyncTask.(map[string]any); ok { - if _, hasId := m["id"]; hasId { - taskId = ToRecord(m).RecordID() - } - } - } - } - - if taskId != 0 { - asyncResult = NewAsyncResult(ctx, taskId, rest) - } - - return asyncResult - -} diff --git a/core/version b/core/version index 2ee7a0d..63e2ef1 100644 --- a/core/version +++ b/core/version @@ -1 +1 @@ -0.109.0 \ No newline at end of file +0.110.0 \ No newline at end of file diff --git a/resources/typed/blockhost_autogen.go b/resources/typed/blockhost_autogen.go index 5f01f79..30d7a99 100644 --- a/resources/typed/blockhost_autogen.go +++ b/resources/typed/blockhost_autogen.go @@ -393,8 +393,7 @@ func (r *BlockHost) BlockHostSetVolumesWithContext_PATCH(ctx context.Context, id return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -437,8 +436,7 @@ func (r *BlockHost) BlockHostUpdateVolumesWithContext_PATCH(ctx context.Context, return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/blockhostmapping_autogen.go b/resources/typed/blockhostmapping_autogen.go index 4d0baf8..55f59df 100644 --- a/resources/typed/blockhostmapping_autogen.go +++ b/resources/typed/blockhostmapping_autogen.go @@ -75,8 +75,7 @@ func (r *BlockHostMapping) BlockHostMappingBulkWithContext_PATCH(ctx context.Con return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/carrier_autogen.go b/resources/typed/carrier_autogen.go index a70ec3e..2644f02 100644 --- a/resources/typed/carrier_autogen.go +++ b/resources/typed/carrier_autogen.go @@ -260,8 +260,7 @@ func (r *Carrier) CarrierHighlightWithContext_PATCH(ctx context.Context, id any, return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -294,8 +293,7 @@ func (r *Carrier) CarrierResetPciWithContext_PATCH(ctx context.Context, id any, return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/cbox_autogen.go b/resources/typed/cbox_autogen.go index 100a543..23223ec 100644 --- a/resources/typed/cbox_autogen.go +++ b/resources/typed/cbox_autogen.go @@ -231,8 +231,7 @@ func (r *Cbox) CboxControlLedWithContext_PATCH(ctx context.Context, id any, cont return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/cluster_autogen.go b/resources/typed/cluster_autogen.go index 9cbe3a3..add3421 100644 --- a/resources/typed/cluster_autogen.go +++ b/resources/typed/cluster_autogen.go @@ -684,8 +684,7 @@ func (r *Cluster) ClusterAddBoxesWithContext_PATCH(ctx context.Context, body *Cl return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -1039,8 +1038,7 @@ func (r *Cluster) ClusterExpandWithContext_POST(ctx context.Context, id any, dev return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -1220,8 +1218,7 @@ func (r *Cluster) ClusterReleaseRecursiveLocksWithContext_DELETE(ctx context.Con return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -1289,8 +1286,7 @@ func (r *Cluster) ClusterRunHardwareCheckWithContext_PATCH(ctx context.Context, return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -1366,8 +1362,7 @@ func (r *Cluster) ClusterSetPasswordWithContext_PATCH(ctx context.Context, id an return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -1402,8 +1397,7 @@ func (r *Cluster) ClusterStopUpgradeWithContext_POST(ctx context.Context, id any return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -1500,8 +1494,7 @@ func (r *Cluster) ClusterUpgradeOptaneWithContext_POST(ctx context.Context, id a return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -1539,8 +1532,7 @@ func (r *Cluster) ClusterUpgradeSsdWithContext_POST(ctx context.Context, id any, return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -1594,8 +1586,7 @@ func (r *Cluster) ClusterUpgradeWithoutFileWithContext_POST(ctx context.Context, return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -1644,8 +1635,7 @@ func (r *Cluster) ClusterUpgradeWithContext_PATCH(ctx context.Context, id any, b return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -1689,8 +1679,7 @@ func (r *Cluster) ClusterUploadFromS3WithContext_POST(ctx context.Context, id an return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -1884,8 +1873,7 @@ func (r *Cluster) ClusterWipeWithContext_POST(ctx context.Context, body *Cluster return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/cnode_autogen.go b/resources/typed/cnode_autogen.go index 1473498..d213ef1 100644 --- a/resources/typed/cnode_autogen.go +++ b/resources/typed/cnode_autogen.go @@ -286,8 +286,7 @@ func (r *Cnode) DeleteByIdWithContext(ctx context.Context, id any, force bool, w return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) } // ----------------------------------------------------- @@ -386,8 +385,7 @@ func (r *Cnode) CnodeAddCnodesWithContext_POST(ctx context.Context, body *CnodeA return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -531,8 +529,7 @@ func (r *Cnode) CnodeHighlightWithContext_PATCH(ctx context.Context, id any, wai return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/dbox_autogen.go b/resources/typed/dbox_autogen.go index fea8f3f..350a196 100644 --- a/resources/typed/dbox_autogen.go +++ b/resources/typed/dbox_autogen.go @@ -177,8 +177,7 @@ func (r *Dbox) DeleteByIdWithContext(ctx context.Context, id any, waitTimeout ti return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) } // Exists checks if a dbox exists @@ -235,8 +234,7 @@ func (r *Dbox) DboxControlLedWithContext_PATCH(ctx context.Context, id any, cont return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -270,8 +268,7 @@ func (r *Dbox) DboxResetDpI2cWithContext_PATCH(ctx context.Context, id any, wait return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/dnode_autogen.go b/resources/typed/dnode_autogen.go index 6dabe74..64d8033 100644 --- a/resources/typed/dnode_autogen.go +++ b/resources/typed/dnode_autogen.go @@ -308,8 +308,7 @@ func (r *Dnode) DnodeHighlightWithContext_PATCH(ctx context.Context, id any, wai return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/ebox_autogen.go b/resources/typed/ebox_autogen.go index ef04e94..e7d37f2 100644 --- a/resources/typed/ebox_autogen.go +++ b/resources/typed/ebox_autogen.go @@ -176,8 +176,7 @@ func (r *Ebox) DeleteByIdWithContext(ctx context.Context, id any, waitTimeout ti return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) } // Exists checks if a ebox exists @@ -234,8 +233,7 @@ func (r *Ebox) EboxControlLedWithContext_PATCH(ctx context.Context, id any, cont return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/eventdefinition_autogen.go b/resources/typed/eventdefinition_autogen.go index d2523fc..4b6d340 100644 --- a/resources/typed/eventdefinition_autogen.go +++ b/resources/typed/eventdefinition_autogen.go @@ -190,8 +190,7 @@ func (r *EventDefinition) EventDefinitionTestWithContext_PATCH(ctx context.Conte return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/globalsnapshotstream_autogen.go b/resources/typed/globalsnapshotstream_autogen.go index 65769cf..67f3d25 100644 --- a/resources/typed/globalsnapshotstream_autogen.go +++ b/resources/typed/globalsnapshotstream_autogen.go @@ -264,8 +264,7 @@ func (r *GlobalSnapshotStream) DeleteByIdWithContext(ctx context.Context, id any return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) } // ----------------------------------------------------- @@ -351,8 +350,7 @@ func (r *GlobalSnapshotStream) GlobalSnapshotStreamStopWithContext_PATCH(ctx con return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/host_autogen.go b/resources/typed/host_autogen.go index 21051ec..ec45194 100644 --- a/resources/typed/host_autogen.go +++ b/resources/typed/host_autogen.go @@ -264,8 +264,7 @@ func (r *Host) HostDiscoverWithContext_GET(ctx context.Context, waitTimeout time return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/nvram_autogen.go b/resources/typed/nvram_autogen.go index 984f4df..0d16ef2 100644 --- a/resources/typed/nvram_autogen.go +++ b/resources/typed/nvram_autogen.go @@ -271,8 +271,7 @@ func (r *Nvram) NvramControlLedWithContext_PATCH(ctx context.Context, id any, co return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -306,8 +305,7 @@ func (r *Nvram) NvramFormatWithContext_PATCH(ctx context.Context, id any, waitTi return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/protectedpath_autogen.go b/resources/typed/protectedpath_autogen.go index d7d4255..0e75af5 100644 --- a/resources/typed/protectedpath_autogen.go +++ b/resources/typed/protectedpath_autogen.go @@ -239,8 +239,7 @@ func (r *ProtectedPath) DeleteByIdWithContext(ctx context.Context, id any, waitT return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) } // ----------------------------------------------------- diff --git a/resources/typed/ssd_autogen.go b/resources/typed/ssd_autogen.go index 9cb05b3..70d47ef 100644 --- a/resources/typed/ssd_autogen.go +++ b/resources/typed/ssd_autogen.go @@ -271,8 +271,7 @@ func (r *Ssd) SsdControlLedWithContext_PATCH(ctx context.Context, id any, contro return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -306,8 +305,7 @@ func (r *Ssd) SsdFormatWithContext_PATCH(ctx context.Context, id any, waitTimeou return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/supportbundles_autogen.go b/resources/typed/supportbundles_autogen.go index 2f49920..9ea5a5b 100644 --- a/resources/typed/supportbundles_autogen.go +++ b/resources/typed/supportbundles_autogen.go @@ -350,8 +350,7 @@ func (r *SupportBundles) SupportBundlesUploadWithContext_PATCH(ctx context.Conte return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/tenant_autogen.go b/resources/typed/tenant_autogen.go index f1c8d05..d51f941 100644 --- a/resources/typed/tenant_autogen.go +++ b/resources/typed/tenant_autogen.go @@ -345,8 +345,7 @@ func (r *Tenant) DeleteByIdWithContext(ctx context.Context, id any, forceRemove return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, record, r.Untyped, waitTimeout) } // ----------------------------------------------------- diff --git a/resources/typed/user_autogen.go b/resources/typed/user_autogen.go index 25a590e..956411b 100644 --- a/resources/typed/user_autogen.go +++ b/resources/typed/user_autogen.go @@ -445,8 +445,7 @@ func (r *User) UserCopyWithContext_POST(ctx context.Context, body *UserCopy_POST return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/view_autogen.go b/resources/typed/view_autogen.go index 38d2d19..f9cf63a 100644 --- a/resources/typed/view_autogen.go +++ b/resources/typed/view_autogen.go @@ -782,8 +782,7 @@ func (r *View) ViewPermissionsRepairWithContext_POST(ctx context.Context, id any return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/vms_autogen.go b/resources/typed/vms_autogen.go index b3b1bc7..0545066 100644 --- a/resources/typed/vms_autogen.go +++ b/resources/typed/vms_autogen.go @@ -408,8 +408,7 @@ func (r *Vms) VmsNetworkSettingsWithContext_PATCH(ctx context.Context, id any, b return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -668,8 +667,7 @@ func (r *Vms) VmsToggleMaintenanceModeWithContext_PATCH(ctx context.Context, id return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/typed/volume_autogen.go b/resources/typed/volume_autogen.go index 784a809..00882b4 100644 --- a/resources/typed/volume_autogen.go +++ b/resources/typed/volume_autogen.go @@ -473,8 +473,7 @@ func (r *Volume) VolumeSetHostsWithContext_PATCH(ctx context.Context, id any, bo return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } @@ -517,8 +516,7 @@ func (r *Volume) VolumeUpdateHostsWithContext_PATCH(ctx context.Context, id any, return nil, err } - asyncResult, _, err := untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) - return asyncResult, err + return untyped.MaybeWaitAsyncResultWithContext(ctx, result, r.Untyped, waitTimeout) } diff --git a/resources/untyped/block_host_autogen.go b/resources/untyped/block_host_autogen.go index f04d44d..39be2be 100644 --- a/resources/untyped/block_host_autogen.go +++ b/resources/untyped/block_host_autogen.go @@ -43,8 +43,7 @@ func (b *BlockHost) BlockHostSetVolumesWithContext_PATCH(ctx context.Context, id return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, b.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, b.Rest, waitTimeout) } @@ -73,8 +72,7 @@ func (b *BlockHost) BlockHostUpdateVolumesWithContext_PATCH(ctx context.Context, return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, b.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, b.Rest, waitTimeout) } diff --git a/resources/untyped/block_host_mapping_autogen.go b/resources/untyped/block_host_mapping_autogen.go index 40d07de..306d853 100644 --- a/resources/untyped/block_host_mapping_autogen.go +++ b/resources/untyped/block_host_mapping_autogen.go @@ -24,8 +24,7 @@ func (b *BlockHostMapping) BlockHostMappingBulkWithContext_PATCH(ctx context.Con return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, b.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, b.Rest, waitTimeout) } diff --git a/resources/untyped/carrier_autogen.go b/resources/untyped/carrier_autogen.go index 24a60f6..68a9da0 100644 --- a/resources/untyped/carrier_autogen.go +++ b/resources/untyped/carrier_autogen.go @@ -53,8 +53,7 @@ func (c *Carrier) CarrierHighlightWithContext_PATCH(ctx context.Context, id any, return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -83,8 +82,7 @@ func (c *Carrier) CarrierResetPciWithContext_PATCH(ctx context.Context, id any, return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } diff --git a/resources/untyped/cbox_autogen.go b/resources/untyped/cbox_autogen.go index b515700..d308f87 100644 --- a/resources/untyped/cbox_autogen.go +++ b/resources/untyped/cbox_autogen.go @@ -29,8 +29,7 @@ func (c *Cbox) CboxControlLedWithContext_PATCH(ctx context.Context, id any, cont return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } diff --git a/resources/untyped/cluster_autogen.go b/resources/untyped/cluster_autogen.go index daf7b1f..9e846ed 100644 --- a/resources/untyped/cluster_autogen.go +++ b/resources/untyped/cluster_autogen.go @@ -24,8 +24,7 @@ func (c *Cluster) ClusterAddBoxesWithContext_PATCH(ctx context.Context, body cor return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -504,8 +503,7 @@ func (c *Cluster) ClusterExpandWithContext_POST(ctx context.Context, id any, dev return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -819,8 +817,7 @@ func (c *Cluster) ClusterReleaseRecursiveLocksWithContext_DELETE(ctx context.Con return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -928,8 +925,7 @@ func (c *Cluster) ClusterRunHardwareCheckWithContext_PATCH(ctx context.Context, return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -1011,8 +1007,7 @@ func (c *Cluster) ClusterSetPasswordWithContext_PATCH(ctx context.Context, id an return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -1064,8 +1059,7 @@ func (c *Cluster) ClusterStopUpgradeWithContext_POST(ctx context.Context, id any return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -1153,8 +1147,7 @@ func (c *Cluster) ClusterUpgradeOptaneWithContext_POST(ctx context.Context, id a return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -1189,8 +1182,7 @@ func (c *Cluster) ClusterUpgradeSsdWithContext_POST(ctx context.Context, id any, return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -1220,8 +1212,7 @@ func (c *Cluster) ClusterUpgradeWithoutFileWithContext_POST(ctx context.Context, return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -1250,8 +1241,7 @@ func (c *Cluster) ClusterUpgradeWithContext_PATCH(ctx context.Context, id any, b return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -1280,8 +1270,7 @@ func (c *Cluster) ClusterUploadFromS3WithContext_POST(ctx context.Context, id an return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -1434,8 +1423,7 @@ func (c *Cluster) ClusterWipeWithContext_POST(ctx context.Context, body core.Par return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } diff --git a/resources/untyped/cnode_autogen.go b/resources/untyped/cnode_autogen.go index 96318aa..7c9d2da 100644 --- a/resources/untyped/cnode_autogen.go +++ b/resources/untyped/cnode_autogen.go @@ -24,8 +24,7 @@ func (c *Cnode) CnodeAddCnodesWithContext_POST(ctx context.Context, body core.Pa return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } @@ -123,8 +122,7 @@ func (c *Cnode) CnodeHighlightWithContext_PATCH(ctx context.Context, id any, bod return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, c.Rest, waitTimeout) } diff --git a/resources/untyped/dbox_autogen.go b/resources/untyped/dbox_autogen.go index 4e54054..ba01a79 100644 --- a/resources/untyped/dbox_autogen.go +++ b/resources/untyped/dbox_autogen.go @@ -50,8 +50,7 @@ func (d *Dbox) DboxControlLedWithContext_PATCH(ctx context.Context, id any, cont return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, d.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, d.Rest, waitTimeout) } @@ -81,8 +80,7 @@ func (d *Dbox) DboxResetDpI2cWithContext_PATCH(ctx context.Context, id any, body return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, d.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, d.Rest, waitTimeout) } diff --git a/resources/untyped/dnode_autogen.go b/resources/untyped/dnode_autogen.go index 7245292..6795cb2 100644 --- a/resources/untyped/dnode_autogen.go +++ b/resources/untyped/dnode_autogen.go @@ -53,8 +53,7 @@ func (d *Dnode) DnodeHighlightWithContext_PATCH(ctx context.Context, id any, bod return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, d.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, d.Rest, waitTimeout) } diff --git a/resources/untyped/ebox_autogen.go b/resources/untyped/ebox_autogen.go index 37ebe50..026626d 100644 --- a/resources/untyped/ebox_autogen.go +++ b/resources/untyped/ebox_autogen.go @@ -63,8 +63,7 @@ func (e *Ebox) EboxControlLedWithContext_PATCH(ctx context.Context, id any, cont return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, e.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, e.Rest, waitTimeout) } diff --git a/resources/untyped/event_definition_autogen.go b/resources/untyped/event_definition_autogen.go index 8caa20a..de29b2a 100644 --- a/resources/untyped/event_definition_autogen.go +++ b/resources/untyped/event_definition_autogen.go @@ -24,8 +24,7 @@ func (e *EventDefinition) EventDefinitionTestWithContext_PATCH(ctx context.Conte return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, e.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, e.Rest, waitTimeout) } diff --git a/resources/untyped/global_snapshot_stream_autogen.go b/resources/untyped/global_snapshot_stream_autogen.go index 34e3eb1..8fd9149 100644 --- a/resources/untyped/global_snapshot_stream_autogen.go +++ b/resources/untyped/global_snapshot_stream_autogen.go @@ -86,8 +86,7 @@ func (g *GlobalSnapshotStream) GlobalSnapshotStreamStopWithContext_PATCH(ctx con return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, g.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, g.Rest, waitTimeout) } diff --git a/resources/untyped/host_autogen.go b/resources/untyped/host_autogen.go index a6ca55f..647a6cb 100644 --- a/resources/untyped/host_autogen.go +++ b/resources/untyped/host_autogen.go @@ -24,8 +24,7 @@ func (h *Host) HostDiscoverWithContext_GET(ctx context.Context, params core.Para return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, h.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, h.Rest, waitTimeout) } diff --git a/resources/untyped/nvram_autogen.go b/resources/untyped/nvram_autogen.go index b3696b3..238f4d8 100644 --- a/resources/untyped/nvram_autogen.go +++ b/resources/untyped/nvram_autogen.go @@ -29,8 +29,7 @@ func (n *Nvram) NvramControlLedWithContext_PATCH(ctx context.Context, id any, co return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, n.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, n.Rest, waitTimeout) } @@ -60,8 +59,7 @@ func (n *Nvram) NvramFormatWithContext_PATCH(ctx context.Context, id any, body c return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, n.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, n.Rest, waitTimeout) } diff --git a/resources/untyped/ssd_autogen.go b/resources/untyped/ssd_autogen.go index 9598de9..58fa836 100644 --- a/resources/untyped/ssd_autogen.go +++ b/resources/untyped/ssd_autogen.go @@ -29,8 +29,7 @@ func (s *Ssd) SsdControlLedWithContext_PATCH(ctx context.Context, id any, contro return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, s.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, s.Rest, waitTimeout) } @@ -60,8 +59,7 @@ func (s *Ssd) SsdFormatWithContext_PATCH(ctx context.Context, id any, body core. return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, s.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, s.Rest, waitTimeout) } diff --git a/resources/untyped/support_bundles_autogen.go b/resources/untyped/support_bundles_autogen.go index 7bf4055..a926305 100644 --- a/resources/untyped/support_bundles_autogen.go +++ b/resources/untyped/support_bundles_autogen.go @@ -45,8 +45,7 @@ func (s *SupportBundles) SupportBundlesUploadWithContext_PATCH(ctx context.Conte return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, s.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, s.Rest, waitTimeout) } diff --git a/resources/untyped/user_autogen.go b/resources/untyped/user_autogen.go index 5dc1a00..cdf4b13 100644 --- a/resources/untyped/user_autogen.go +++ b/resources/untyped/user_autogen.go @@ -112,8 +112,7 @@ func (u *User) UserCopyWithContext_POST(ctx context.Context, body core.Params, w return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, u.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, u.Rest, waitTimeout) } diff --git a/resources/untyped/v_task.go b/resources/untyped/v_task.go index 70796ce..8391aae 100644 --- a/resources/untyped/v_task.go +++ b/resources/untyped/v_task.go @@ -3,7 +3,6 @@ package untyped import ( "context" "fmt" - "strings" "time" "github.com/vast-data/go-vast-client/core" @@ -17,89 +16,53 @@ type ( AsyncResult = core.AsyncResult ) -// nextBackoff returns the next polling interval using additive backoff strategy. +// WaitTaskWithContext polls the task status until it completes, fails, or times out. // -// It increases the current interval by 250ms up to a given max value. -// -// Parameters: -// - current: the current polling interval. -// - max: the maximum allowed interval. -// -// Returns: -// - time.Duration: the next interval to wait before polling again. -func nextBackoff(current, max time.Duration) time.Duration { - next := current + 250*time.Millisecond - if next > max { - return max - } - return next -} - -// WaitTaskWithContext polls the task status until it completes, fails, or the context expires. -// -// It starts with a 500ms polling interval and increases it slightly after each attempt, -// using exponential-style backoff (capped at 5 seconds). This reduces the load on the API -// during long-running tasks. +// This method creates an AsyncResult and uses its Wait() method to poll the task status +// with exponential backoff. The AsyncResult.Success and AsyncResult.Err fields are updated +// based on the task outcome. // // Task states: -// - "completed" → returns the task Record. -// - "running" → continues polling. -// - any other state → considered failure, and returns the last message from the task. -// -// If the context deadline is exceeded or canceled, the method returns an error with context cause. +// - "completed" → returns the task Record with AsyncResult.Success=true. +// - "running" → continues polling with exponential backoff. +// - any other state → considered failure, returns error with AsyncResult.Success=false. // // Parameters: -// - ctx: context with optional timeout or cancellation. +// - ctx: context for cancellation control. // - taskId: unique identifier of the task to wait for. +// - timeout: maximum duration to wait for task completion (0 uses default of 10 minutes). // // Returns: // - Record: the completed task record, if successful. -// - error: if the task failed, context expired, or an API error occurred. -func (t *VTask) WaitTaskWithContext(ctx context.Context, taskId int64) (core.Record, error) { +// - error: if the task failed, timeout occurred, or an API error occurred. +func (t *VTask) WaitTaskWithContext(ctx context.Context, taskId int64, timeout time.Duration) (core.Record, error) { if t == nil { return nil, fmt.Errorf("VTask is nil") } - baseInterval := 500 * time.Millisecond - maxInterval := 5 * time.Second - currentInterval := baseInterval - - for { - select { - case <-ctx.Done(): - return nil, fmt.Errorf("timeout waiting for task %d: %w", taskId, ctx.Err()) - - default: - task, err := t.GetByIdWithContext(ctx, taskId) - if err != nil { - return nil, err - } - - state := strings.ToLower(fmt.Sprintf("%v", task["state"])) - switch state { - case "completed": - return task, nil - case "running": - // backoff - time.Sleep(currentInterval) - currentInterval = nextBackoff(currentInterval, maxInterval) - default: - rawMessages := task["messages"] - messages, ok := rawMessages.([]interface{}) - if !ok || len(messages) == 0 { - return nil, fmt.Errorf("task %s failed with ID %d: no messages or unexpected format", task.RecordName(), task.RecordID()) - } - lastMsg := fmt.Sprintf("%v", messages[len(messages)-1]) - return nil, fmt.Errorf("task %s failed with ID %d: %s", task.RecordName(), task.RecordID(), lastMsg) - } - } + // Create AsyncResult and reuse its Wait() logic + asyncResult := &core.AsyncResult{ + TaskId: taskId, + Rest: t.Rest, + Ctx: ctx, } + + return asyncResult.Wait(timeout) } +// WaitTask polls the task status until it completes, fails, or times out. +// +// This is a convenience wrapper around WaitTaskWithContext that uses the bound REST context. +// +// Parameters: +// - taskId: unique identifier of the task to wait for. +// - timeout: maximum duration to wait for task completion (0 uses default of 10 minutes). +// +// Returns: +// - Record: the completed task record, if successful. +// - error: if the task failed, timeout occurred, or an API error occurred. func (t *VTask) WaitTask(taskId int64, timeout time.Duration) (core.Record, error) { - ctx, cancel := context.WithTimeout(t.Rest.GetCtx(), timeout) - defer cancel() - return t.WaitTaskWithContext(ctx, taskId) + return t.WaitTaskWithContext(t.Rest.GetCtx(), taskId, timeout) } // MaybeWaitAsyncResult checks if the record represents an async task result and optionally waits for it to complete. @@ -114,7 +77,7 @@ func (t *VTask) WaitTask(taskId int64, timeout time.Duration) (core.Record, erro // - *AsyncResult: The async result if one was found, nil otherwise // - core.Record: The completed task record if timeout > 0 and task completed successfully, nil otherwise // - error: Any error that occurred during waiting -func MaybeWaitAsyncResult(record core.Record, rest core.VastRest, timeout time.Duration) (*AsyncResult, core.Record, error) { +func MaybeWaitAsyncResult(record core.Record, rest core.VastRest, timeout time.Duration) (*AsyncResult, error) { return MaybeWaitAsyncResultWithContext(rest.GetCtx(), record, rest, timeout) } @@ -128,33 +91,29 @@ func MaybeWaitAsyncResult(record core.Record, rest core.VastRest, timeout time.D // - If timeout > 0, waits for task completion and returns (*AsyncResult, taskRecord, error) // // Parameters: -// - ctx: The context to use for waiting (will be wrapped with timeout if timeout > 0) +// - ctx: The context to use for waiting (cancellation and timeout control) // - record: The record that may contain an async task response // - rest: The REST client to use for waiting on the task // - timeout: If 0, returns immediately without waiting. Otherwise, waits for task completion with the specified timeout. // // Returns: -// - *AsyncResult: The async result if one was found, nil otherwise +// - *AsyncResult: The async result if one was found, nil otherwise (Success and Err fields populated after wait) // - core.Record: The completed task record if timeout > 0 and task completed successfully, nil otherwise -// - error: Any error that occurred during waiting -func MaybeWaitAsyncResultWithContext(ctx context.Context, record core.Record, rest core.VastRest, timeout time.Duration) (*AsyncResult, core.Record, error) { - var ( - asyncResult *AsyncResult - taskResponse core.Record - err error - ) - asyncResult = core.MaybeAsyncResultFromRecord(ctx, record, rest) - if asyncResult != nil && timeout > 0 { - ctx, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - taskResponse, err = rest.GetResourceMap()["VTask"].(*VTask).WaitTaskWithContext(ctx, asyncResult.TaskId) - if err == nil && taskResponse != nil { - if state, ok := taskResponse["state"]; ok { - asyncResult.Status = state.(string) - } - } +// - error: Any error that occurred during waiting (same as AsyncResult.Err) +func MaybeWaitAsyncResultWithContext(ctx context.Context, record core.Record, rest core.VastRest, timeout time.Duration) (*AsyncResult, error) { + asyncResult := core.MaybeAsyncResultFromRecord(ctx, record, rest) + if asyncResult == nil { + return nil, nil + } + + // If timeout is 0, return immediately (fire and forget) + if timeout == 0 { + return asyncResult, nil } - return asyncResult, taskResponse, err + // Wait for task completion using AsyncResult.Wait() + // This will populate asyncResult.Success and asyncResult.Err + _, err := asyncResult.Wait(timeout) + return asyncResult, err } diff --git a/resources/untyped/view_autogen.go b/resources/untyped/view_autogen.go index 2121ce2..8bc7a51 100644 --- a/resources/untyped/view_autogen.go +++ b/resources/untyped/view_autogen.go @@ -183,8 +183,7 @@ func (v *View) ViewPermissionsRepairWithContext_POST(ctx context.Context, id any return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, v.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, v.Rest, waitTimeout) } diff --git a/resources/untyped/vms_autogen.go b/resources/untyped/vms_autogen.go index b5659d7..815a99f 100644 --- a/resources/untyped/vms_autogen.go +++ b/resources/untyped/vms_autogen.go @@ -109,8 +109,7 @@ func (v *Vms) VmsNetworkSettingsWithContext_PATCH(ctx context.Context, id any, b return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, v.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, v.Rest, waitTimeout) } @@ -493,8 +492,7 @@ func (v *Vms) VmsToggleMaintenanceModeWithContext_PATCH(ctx context.Context, id return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, v.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, v.Rest, waitTimeout) } diff --git a/resources/untyped/volume_autogen.go b/resources/untyped/volume_autogen.go index b768515..243d0a9 100644 --- a/resources/untyped/volume_autogen.go +++ b/resources/untyped/volume_autogen.go @@ -86,8 +86,7 @@ func (v *Volume) VolumeSetHostsWithContext_PATCH(ctx context.Context, id any, bo return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, v.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, v.Rest, waitTimeout) } @@ -116,8 +115,7 @@ func (v *Volume) VolumeUpdateHostsWithContext_PATCH(ctx context.Context, id any, return nil, err } - asyncResult, _, err := MaybeWaitAsyncResultWithContext(ctx, result, v.Rest, waitTimeout) - return asyncResult, err + return MaybeWaitAsyncResultWithContext(ctx, result, v.Rest, waitTimeout) }