Skip to content

Commit 0a1dd34

Browse files
authored
Merge pull request #34 from ScaleComputing/fix-vm-shutdown
Fix vm shutdown
2 parents f086cba + a1e7a08 commit 0a1dd34

File tree

4 files changed

+138
-27
lines changed

4 files changed

+138
-27
lines changed

internal/provider/hypercore_vm_power_state_resource.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,7 @@ func (r *HypercoreVMPowerStateResource) Create(ctx context.Context, req resource
125125
// we need to check with the NEEDED_ACTION_FOR_POWER_STATE.
126126
// actionType := utils.NEEDED_ACTION_FOR_POWER_STATE[data.State.ValueString()]
127127
actionType := utils.GetNeededActionForState(data.State.ValueString(), data.ForceSutoff.ValueBool())
128-
createPayload := []map[string]any{
129-
{
130-
"virDomainUUID": data.VmUUID.ValueString(),
131-
"actionType": actionType,
132-
"cause": "INTERNAL",
133-
},
134-
}
135-
diag := utils.ModifyVMPowerState(*r.client, data.VmUUID.ValueString(), createPayload, ctx)
128+
diag := utils.ModifyVMPowerState(*r.client, data.VmUUID.ValueString(), actionType, ctx)
136129
if diag != nil {
137130
resp.Diagnostics.AddWarning(diag.Summary(), diag.Detail())
138131
}
@@ -248,14 +241,7 @@ func (r *HypercoreVMPowerStateResource) Update(ctx context.Context, req resource
248241
// we need to check with the NEEDED_ACTION_FOR_POWER_STATE.
249242
// actionType := utils.NEEDED_ACTION_FOR_POWER_STATE[vmDesiredState]
250243
actionType := utils.GetNeededActionForState(vmDesiredState, forceShutoff)
251-
updatePayload := []map[string]any{
252-
{
253-
"virDomainUUID": vmUUID,
254-
"actionType": actionType,
255-
"cause": "INTERNAL",
256-
},
257-
}
258-
diag := utils.ModifyVMPowerState(restClient, vmUUID, updatePayload, ctx)
244+
diag := utils.ModifyVMPowerState(restClient, vmUUID, actionType, ctx)
259245
if diag != nil {
260246
resp.Diagnostics.AddWarning(diag.Summary(), diag.Detail())
261247
}

internal/provider/hypercore_vm_resource.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ package provider
66
import (
77
"context"
88
"fmt"
9+
"os"
10+
"strconv"
11+
"time"
912

1013
"github.com/hashicorp/terraform-plugin-framework/attr"
14+
"github.com/hashicorp/terraform-plugin-framework/diag"
1115
"github.com/hashicorp/terraform-plugin-framework/path"
1216
"github.com/hashicorp/terraform-plugin-framework/resource"
1317
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
@@ -393,6 +397,12 @@ func (r *HypercoreVMResource) Delete(ctx context.Context, req resource.DeleteReq
393397

394398
restClient := *r.client
395399
vm_uuid := data.Id.ValueString()
400+
err := shutdownVM(ctx, vm_uuid, &restClient)
401+
if err != nil {
402+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete shutdown VM, got error: %s", err))
403+
return
404+
}
405+
396406
taskTag := restClient.DeleteRecord(
397407
fmt.Sprintf("/rest/v1/VirDomain/%s", vm_uuid),
398408
-1,
@@ -405,3 +415,91 @@ func (r *HypercoreVMResource) ImportState(ctx context.Context, req resource.Impo
405415
tflog.Info(ctx, "TTRT HypercoreVMResource IMPORT_STATE")
406416
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
407417
}
418+
419+
func shutdownVM(ctx context.Context, vmUUID string, restClient *utils.RestClient) diag.Diagnostic {
420+
currentState, err := utils.GetVMPowerState(vmUUID, *restClient)
421+
if err != nil {
422+
return err
423+
}
424+
if currentState == "SHUTOFF" {
425+
return nil
426+
}
427+
// VM needs to be shutdown
428+
429+
// VM shutdown might be already initiated, but not yet fully done.
430+
// Send ACPI shutdown if needed.
431+
desiredState, err := utils.GetVMDesiredState(vmUUID, *restClient)
432+
if err != nil {
433+
return err
434+
}
435+
if desiredState != "SHUTOFF" {
436+
err = utils.ModifyVMPowerState(*restClient, vmUUID, "SHUTDOWN", ctx)
437+
if err != nil {
438+
return err
439+
}
440+
}
441+
var maxWaitTime int = 300
442+
envMaxWaitTime := os.Getenv("HC_VM_SHUTDOWN_TIMEOUT")
443+
if envMaxWaitTime != "" {
444+
val, err := strconv.Atoi(envMaxWaitTime)
445+
if err != nil {
446+
err_msg := fmt.Sprintf("TTRT HypercoreVMResource Destroy, environ HC_VM_SHUTDOWN_TIMEOUT=%s is not number, err=%s", envMaxWaitTime, err)
447+
tflog.Error(ctx, err_msg)
448+
var diags diag.Diagnostics
449+
diags.AddError(
450+
"Invalid environ variable HC_VM_SHUTDOWN_TIMEOUT",
451+
err_msg,
452+
)
453+
return diags[0]
454+
}
455+
maxWaitTime = val
456+
}
457+
var waitSleep = 5
458+
var waitTime = 0
459+
for {
460+
// wait on VM to stop
461+
currentState, err := utils.GetVMPowerState(vmUUID, *restClient)
462+
if err != nil {
463+
return err
464+
}
465+
if currentState == "SHUTOFF" {
466+
return nil
467+
}
468+
if waitTime > maxWaitTime {
469+
break
470+
}
471+
time.Sleep(time.Duration(waitSleep) * time.Second)
472+
waitTime += waitSleep
473+
}
474+
475+
// force shutdown is needed
476+
tflog.Warn(ctx, "TTRT HypercoreVMResource Destroy, VM is still running after nice ACPI shutdown, VM will be force shutoff.")
477+
err = utils.ModifyVMPowerState(*restClient, vmUUID, "STOP", ctx)
478+
if err != nil {
479+
return err
480+
}
481+
waitTime = 0
482+
for {
483+
// wait on VM to stop
484+
currentState, err := utils.GetVMPowerState(vmUUID, *restClient)
485+
if err != nil {
486+
return err
487+
}
488+
if currentState == "SHUTOFF" {
489+
return nil
490+
}
491+
if waitTime > maxWaitTime {
492+
break
493+
}
494+
time.Sleep(time.Duration(waitSleep) * time.Second)
495+
waitTime += waitSleep
496+
}
497+
498+
tflog.Error(ctx, "TTRT HypercoreVMResource Destroy, VM is still running after force shutdown")
499+
var diags diag.Diagnostics
500+
diags.AddError(
501+
"Error Shutting down VM",
502+
"Unable to shutdown VM with ACPI shutdown or force shutdown.",
503+
)
504+
return diags[0]
505+
}

internal/utils/vm_power_state.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,17 @@ func GetNeededActionForState(desiredState string, forceShutoff bool) string {
3333
func ModifyVMPowerState(
3434
restClient RestClient,
3535
vmUUID string,
36-
payload []map[string]any,
36+
actionType string,
3737
ctx context.Context,
3838
) diag.Diagnostic {
3939

40+
payload := []map[string]any{
41+
{
42+
"virDomainUUID": vmUUID,
43+
"actionType": actionType,
44+
"cause": "INTERNAL",
45+
},
46+
}
4047
taskTag, _, err := restClient.CreateRecordWithList(
4148
"/rest/v1/VirDomain/action",
4249
payload,
@@ -70,19 +77,19 @@ func GetVMPowerState(vmUUID string, restClient RestClient) (string, diag.Diagnos
7077
return powerState, nil
7178
}
7279

73-
func GetVMDesiredState(vmUUID string, restClient RestClient) (*string, diag.Diagnostic) {
80+
func GetVMDesiredState(vmUUID string, restClient RestClient) (string, diag.Diagnostic) {
7481
vm, err := GetOneVMWithError(vmUUID, restClient)
7582

7683
if err != nil {
77-
return nil, diag.NewErrorDiagnostic(
84+
return "", diag.NewErrorDiagnostic(
7885
"VM not found",
7986
err.Error(),
8087
)
8188
}
8289

8390
powerState := AnyToString((*vm)["desiredDisposition"])
8491

85-
return &powerState, nil
92+
return powerState, nil
8693
}
8794

8895
func ValidatePowerState(desiredState string) diag.Diagnostic {

local/main.tf

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,38 @@ terraform {
99
}
1010
}
1111

12+
locals {
13+
vm_name = "testtf-remove-running"
14+
src_vm_name = "testtf-src-empty"
15+
}
16+
1217
provider "hypercore" {}
1318

14-
data "hypercore_remote_cluster_connection" "all_clusters" {}
19+
data "hypercore_vm" "src_empty" {
20+
name = local.src_vm_name
21+
}
1522

16-
data "hypercore_remote_cluster_connection" "cluster-a" {
17-
remote_cluster_name = "cluster-a"
23+
resource "hypercore_vm" "vm_on" {
24+
group = "testtf"
25+
name = local.vm_name
26+
description = "VM to be removed"
27+
vcpu = 1
28+
memory = 1234 # MiB
29+
clone = {
30+
source_vm_uuid = data.hypercore_vm.src_empty.vms.0.uuid
31+
meta_data = ""
32+
user_data = ""
33+
}
1834
}
1935

20-
output "all_remote_clusters" {
21-
value = jsonencode(data.hypercore_remote_cluster_connection.all_clusters)
36+
resource "hypercore_vm_power_state" "vm_on" {
37+
vm_uuid = hypercore_vm.vm_on.id
38+
state = "RUNNING"
2239
}
2340

24-
output "filtered_remote_cluster" {
25-
value = jsonencode(data.hypercore_remote_cluster_connection.cluster-a)
41+
output "vm_on_uuid" {
42+
value = hypercore_vm.vm_on.id
43+
}
44+
output "power_state" {
45+
value = hypercore_vm_power_state.vm_on.state
2646
}

0 commit comments

Comments
 (0)