Skip to content

Commit 3752b98

Browse files
Merge branch 'master' into master
2 parents 532eb0e + 4c63c80 commit 3752b98

1 file changed

Lines changed: 132 additions & 40 deletions

File tree

nutanix/services/vmmv2/resource_nutanix_virtual_machine_v2.go

Lines changed: 132 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,29 +1392,18 @@ func ResourceNutanixVirtualMachineV2Create(ctx context.Context, d *schema.Resour
13921392
}
13931393
d.SetId(utils.StringValue(uuid))
13941394

1395-
// read VM
1396-
1397-
readResp, errR := conn.VMAPIInstance.GetVmById(utils.StringPtr(*uuid))
1398-
if errR != nil {
1399-
return diag.Errorf("error while reading vm : %v", errR)
1400-
}
1401-
args := make(map[string]interface{})
1402-
args["If-Match"] = getEtagHeader(readResp, conn)
1403-
14041395
var PowerTaskRef import1.TaskReference
14051396
if powerState, ok := d.GetOk("power_state"); ok {
1406-
if powerState == "ON" {
1407-
resp, err := conn.VMAPIInstance.PowerOnVm(uuid, args)
1408-
if err != nil {
1409-
return diag.Errorf("error while powering on Virtual Machines : %v", err)
1410-
}
1411-
PowerTaskRef = resp.Data.GetValue().(import1.TaskReference)
1412-
} else if powerState == "OFF" {
1413-
resp, err := conn.VMAPIInstance.PowerOffVm(utils.StringPtr(d.Id()), args)
1414-
if err != nil {
1415-
return diag.Errorf("error while powering OFF : %v", err)
1416-
}
1417-
PowerTaskRef = resp.Data.GetValue().(import1.TaskReference)
1397+
switch powerState {
1398+
case "ON":
1399+
PowerTaskRef, err = powerOnVMWithRetry(ctx, conn, utils.StringPtr(d.Id()))
1400+
case "OFF":
1401+
PowerTaskRef, err = powerOffVMWithRetry(ctx, conn, utils.StringPtr(d.Id()))
1402+
default:
1403+
return diag.Errorf("invalid power state: %s", powerState)
1404+
}
1405+
if err != nil {
1406+
return diag.Errorf("error while powering %s Virtual Machines: %v", powerState, err)
14181407
}
14191408
}
14201409
powertaskUUID := PowerTaskRef.ExtId
@@ -3175,6 +3164,121 @@ func expandPolicyReference(pr interface{}) *config.PolicyReference {
31753164
return nil
31763165
}
31773166

3167+
// extractTaskReferenceFromResponse extracts TaskReference from API response using reflection
3168+
func extractTaskReferenceFromResponse(resp interface{}) (import1.TaskReference, error) {
3169+
respValue := reflect.ValueOf(resp)
3170+
if respValue.Kind() == reflect.Ptr {
3171+
respValue = respValue.Elem()
3172+
}
3173+
dataField := respValue.FieldByName("Data")
3174+
if !dataField.IsValid() {
3175+
return import1.TaskReference{}, fmt.Errorf("unexpected response structure: Data field not found")
3176+
}
3177+
getValueMethod := dataField.MethodByName("GetValue")
3178+
if !getValueMethod.IsValid() {
3179+
return import1.TaskReference{}, fmt.Errorf("unexpected response structure: GetValue method not found")
3180+
}
3181+
result := getValueMethod.Call(nil)[0].Interface()
3182+
taskRef, ok := result.(import1.TaskReference)
3183+
if !ok {
3184+
return import1.TaskReference{}, fmt.Errorf("unexpected response type: expected TaskReference")
3185+
}
3186+
return taskRef, nil
3187+
}
3188+
3189+
// powerOnVMWithRetry attempts to power on a VM with retry logic
3190+
// It fetches the VM and ETag header for each retry attempt to ensure we have the latest ETag
3191+
func powerOnVMWithRetry(ctx context.Context, conn *vmm.Client, vmID *string) (import1.TaskReference, error) {
3192+
maxRetries := 10
3193+
retryDelay := 2500 * time.Millisecond // 2.5 seconds
3194+
var resp interface{}
3195+
var err error
3196+
3197+
for attempt := 0; attempt < maxRetries; attempt++ {
3198+
// Fetch VM to get latest ETag for each retry attempt
3199+
readResp, errR := conn.VMAPIInstance.GetVmById(vmID)
3200+
if errR != nil {
3201+
return import1.TaskReference{}, fmt.Errorf("error while fetching vm : %v", errR)
3202+
}
3203+
3204+
// Build args with fresh ETag for this attempt
3205+
args := make(map[string]interface{})
3206+
args["If-Match"] = getEtagHeader(readResp, conn)
3207+
3208+
resp, err = conn.VMAPIInstance.PowerOnVm(vmID, args)
3209+
if err == nil {
3210+
break
3211+
}
3212+
3213+
if attempt < maxRetries {
3214+
log.Printf("[DEBUG] Attempt %d/%d failed to power on VM, retrying in %v: %v", attempt+1, maxRetries, retryDelay, err)
3215+
select {
3216+
case <-ctx.Done():
3217+
return import1.TaskReference{}, fmt.Errorf("context cancelled while powering on VM: %v", ctx.Err())
3218+
case <-time.After(retryDelay):
3219+
// Continue to next retry
3220+
}
3221+
}
3222+
}
3223+
3224+
if err != nil {
3225+
return import1.TaskReference{}, fmt.Errorf("error while powering on Virtual Machine after %d attempts: %v", maxRetries, err)
3226+
}
3227+
3228+
taskRef, err := extractTaskReferenceFromResponse(resp)
3229+
if err != nil {
3230+
return import1.TaskReference{}, fmt.Errorf("error extracting task reference from power on response: %v", err)
3231+
}
3232+
log.Printf("[DEBUG] PowerOn Response: TaskReference ExtId: %s", utils.StringValue(taskRef.ExtId))
3233+
return taskRef, nil
3234+
}
3235+
3236+
// powerOffVMWithRetry attempts to power off a VM with retry logic
3237+
// It fetches the VM and ETag header for each retry attempt to ensure we have the latest ETag
3238+
func powerOffVMWithRetry(ctx context.Context, conn *vmm.Client, vmID *string) (import1.TaskReference, error) {
3239+
maxRetries := 10
3240+
retryDelay := 2500 * time.Millisecond // 2.5 seconds
3241+
var resp interface{}
3242+
var err error
3243+
3244+
for attempt := 0; attempt < maxRetries; attempt++ {
3245+
// Fetch VM to get latest ETag for each retry attempt
3246+
readResp, errR := conn.VMAPIInstance.GetVmById(vmID)
3247+
if errR != nil {
3248+
return import1.TaskReference{}, fmt.Errorf("error while fetching vm : %v", errR)
3249+
}
3250+
3251+
// Build args with fresh ETag for this attempt
3252+
args := make(map[string]interface{})
3253+
args["If-Match"] = getEtagHeader(readResp, conn)
3254+
3255+
resp, err = conn.VMAPIInstance.PowerOffVm(vmID, args)
3256+
if err == nil {
3257+
break
3258+
}
3259+
3260+
if attempt < maxRetries {
3261+
log.Printf("[DEBUG] Attempt %d/%d failed to power off VM, retrying in %v: %v", attempt+1, maxRetries, retryDelay, err)
3262+
select {
3263+
case <-ctx.Done():
3264+
return import1.TaskReference{}, fmt.Errorf("context cancelled while powering off VM: %v", ctx.Err())
3265+
case <-time.After(retryDelay):
3266+
// Continue to next retry
3267+
}
3268+
}
3269+
}
3270+
3271+
if err != nil {
3272+
return import1.TaskReference{}, fmt.Errorf("error while powering off Virtual Machine after %d attempts: %v", maxRetries, err)
3273+
}
3274+
taskRef, err := extractTaskReferenceFromResponse(resp)
3275+
if err != nil {
3276+
return import1.TaskReference{}, fmt.Errorf("error extracting task reference from power off response: %v", err)
3277+
}
3278+
log.Printf("[DEBUG] PowerOff Response: TaskReference ExtId: %s", utils.StringValue(taskRef.ExtId))
3279+
return taskRef, nil
3280+
}
3281+
31783282
func flattenPowerState(pr *config.PowerState) string {
31793283
if pr != nil {
31803284
const two, three, four, five = 2, 3, 4, 5
@@ -3207,17 +3311,11 @@ func callForPowerOffVM(ctx context.Context, conn *vmm.Client, d *schema.Resource
32073311
return nil
32083312
}
32093313

3210-
// Extract E-Tag Header
3211-
args := make(map[string]interface{})
3212-
args["If-Match"] = getEtagHeader(readResp, conn)
3213-
3214-
// Power off the VM
3215-
powerOffResp, err := conn.VMAPIInstance.PowerOffVm(utils.StringPtr(d.Id()), args)
3314+
// Power off the VM with retry logic (ETag is fetched inside the retry function)
3315+
TaskRef, err := powerOffVMWithRetry(ctx, conn, utils.StringPtr(d.Id()))
32163316
if err != nil {
3217-
return diag.Errorf("error while powering off Virtual Machine : %v", err)
3317+
return diag.Errorf("error while powering off Virtual Machine: %v", err)
32183318
}
3219-
3220-
TaskRef := powerOffResp.Data.GetValue().(import1.TaskReference)
32213319
taskUUID := TaskRef.ExtId
32223320

32233321
prismConn := meta.(*conns.Client).PrismAPI
@@ -3249,19 +3347,13 @@ func callForPowerOnVM(ctx context.Context, conn *vmm.Client, d *schema.ResourceD
32493347
return nil
32503348
}
32513349

3252-
// Extract E-Tag Header
3253-
args := make(map[string]interface{})
3254-
args["If-Match"] = getEtagHeader(readResp, conn)
3255-
// Power on the VM
3256-
powerOnResp, err := conn.VMAPIInstance.PowerOnVm(utils.StringPtr(d.Id()), args)
3350+
// Power on the VM with retry logic (ETag is fetched inside the retry function)
3351+
TaskRef, err := powerOnVMWithRetry(ctx, conn, utils.StringPtr(d.Id()))
32573352
if err != nil {
3258-
return diag.Errorf("error while powering on Virtual Machine : %v", err)
3353+
return diag.Errorf("error while powering on Virtual Machine: %v", err)
32593354
}
32603355

3261-
aJSON, _ := json.Marshal(powerOnResp)
3262-
log.Printf("[DEBUG] PowerOn Response: %s", string(aJSON))
3263-
3264-
TaskRef := powerOnResp.Data.GetValue().(import1.TaskReference)
3356+
log.Printf("[DEBUG] PowerOn Response: TaskReference ExtId: %s", utils.StringValue(TaskRef.ExtId))
32653357
taskUUID := TaskRef.ExtId
32663358

32673359
prismConn := meta.(*conns.Client).PrismAPI

0 commit comments

Comments
 (0)