Skip to content

Commit a1e7a08

Browse files
committed
Shutdown VM before destroy
Signed-off-by: Justin Cinkelj <justin.cinkelj@xlab.si>
1 parent 9acd85e commit a1e7a08

File tree

3 files changed

+128
-10
lines changed

3 files changed

+128
-10
lines changed

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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,19 @@ func GetVMPowerState(vmUUID string, restClient RestClient) (string, diag.Diagnos
7777
return powerState, nil
7878
}
7979

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

8383
if err != nil {
84-
return nil, diag.NewErrorDiagnostic(
84+
return "", diag.NewErrorDiagnostic(
8585
"VM not found",
8686
err.Error(),
8787
)
8888
}
8989

9090
powerState := AnyToString((*vm)["desiredDisposition"])
9191

92-
return &powerState, nil
92+
return powerState, nil
9393
}
9494

9595
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)