Skip to content

Commit 32c20a1

Browse files
committed
feat: add experimental domain destroy shutdown mode
1 parent 9cc319e commit 32c20a1

File tree

2 files changed

+171
-10
lines changed

2 files changed

+171
-10
lines changed

internal/provider/domain_resource.go

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,19 @@ type DomainCreateModel struct {
6969

7070
// DomainDestroyModel describes domain shutdown behavior
7171
type DomainDestroyModel struct {
72-
Graceful types.Bool `tfsdk:"graceful"`
72+
Graceful types.Bool `tfsdk:"graceful"`
73+
Shutdown types.Object `tfsdk:"shutdown"`
74+
}
75+
76+
// DomainDestroyShutdownModel describes optional shutdown wait behavior.
77+
type DomainDestroyShutdownModel struct {
78+
Timeout types.Int64 `tfsdk:"timeout"`
79+
}
80+
81+
type domainDestroyOptions struct {
82+
Flags golibvirt.DomainDestroyFlagsValues
83+
ShutdownEnabled bool
84+
ShutdownTimeout time.Duration
7385
}
7486

7587
type domainPlanData struct {
@@ -446,6 +458,42 @@ func domainDestroyFlagsFromDestroy(ctx context.Context, destroyVal types.Object)
446458
return flags, nil
447459
}
448460

461+
func domainDestroyOptionsFromDestroy(ctx context.Context, destroyVal types.Object) (domainDestroyOptions, diag.Diagnostics) {
462+
flags, diags := domainDestroyFlagsFromDestroy(ctx, destroyVal)
463+
options := domainDestroyOptions{
464+
Flags: flags,
465+
}
466+
if diags.HasError() || destroyVal.IsNull() || destroyVal.IsUnknown() {
467+
return options, diags
468+
}
469+
470+
var destroyModel DomainDestroyModel
471+
diags = destroyVal.As(ctx, &destroyModel, basetypes.ObjectAsOptions{})
472+
if diags.HasError() {
473+
return options, diags
474+
}
475+
476+
if !destroyModel.Shutdown.IsNull() && !destroyModel.Shutdown.IsUnknown() {
477+
options.ShutdownEnabled = true
478+
options.ShutdownTimeout = 30 * time.Second
479+
480+
var shutdownModel DomainDestroyShutdownModel
481+
diags = destroyModel.Shutdown.As(ctx, &shutdownModel, basetypes.ObjectAsOptions{})
482+
if diags.HasError() {
483+
return options, diags
484+
}
485+
486+
if !shutdownModel.Timeout.IsNull() && !shutdownModel.Timeout.IsUnknown() {
487+
timeout := shutdownModel.Timeout.ValueInt64()
488+
if timeout > 0 {
489+
options.ShutdownTimeout = time.Duration(timeout) * time.Second
490+
}
491+
}
492+
}
493+
494+
return options, nil
495+
}
496+
449497
// Metadata returns the resource type name
450498
func (r *DomainResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
451499
resp.TypeName = req.ProviderTypeName + "_domain"
@@ -479,7 +527,20 @@ func (r *DomainResource) Schema(ctx context.Context, req resource.SchemaRequest,
479527
Description: "Destroy behavior when Terraform removes the domain.",
480528
Optional: true,
481529
Attributes: map[string]schema.Attribute{
482-
"graceful": schema.BoolAttribute{Optional: true},
530+
"graceful": schema.BoolAttribute{
531+
Description: "Experimental: request graceful behavior when using DomainDestroyFlags during domain stop. Subject to change in future releases.",
532+
Optional: true,
533+
},
534+
"shutdown": schema.SingleNestedAttribute{
535+
Description: "Experimental: request a guest shutdown and wait for shutoff before undefine. Subject to change in future releases.",
536+
Optional: true,
537+
Attributes: map[string]schema.Attribute{
538+
"timeout": schema.Int64Attribute{
539+
Description: "Experimental: seconds to wait for guest shutdown before failing destroy. Defaults to 30.",
540+
Optional: true,
541+
},
542+
},
543+
},
483544
},
484545
},
485546
}
@@ -1015,7 +1076,7 @@ func (r *DomainResource) Delete(ctx context.Context, req resource.DeleteRequest,
10151076
return
10161077
}
10171078

1018-
destroyFlags, destroyDiags := domainDestroyFlagsFromDestroy(ctx, state.Destroy)
1079+
destroyOptions, destroyDiags := domainDestroyOptionsFromDestroy(ctx, state.Destroy)
10191080
resp.Diagnostics.Append(destroyDiags...)
10201081
if resp.Diagnostics.HasError() {
10211082
return
@@ -1040,13 +1101,33 @@ func (r *DomainResource) Delete(ctx context.Context, req resource.DeleteRequest,
10401101

10411102
// DomainState values: 0=nostate, 1=running, 2=blocked, 3=paused, 4=shutdown, 5=shutoff, 6=crashed, 7=pmsuspended
10421103
if uint32(domainState) == uint32(golibvirt.DomainRunning) {
1043-
err = r.client.Libvirt().DomainDestroyFlags(domain, destroyFlags)
1044-
if err != nil {
1045-
resp.Diagnostics.AddError(
1046-
"Failed to Destroy Domain",
1047-
"Failed to stop running domain: "+err.Error(),
1048-
)
1049-
return
1104+
if destroyOptions.ShutdownEnabled {
1105+
err = r.client.Libvirt().DomainShutdown(domain)
1106+
if err != nil {
1107+
resp.Diagnostics.AddError(
1108+
"Failed to Shutdown Domain",
1109+
"Failed to request guest shutdown: "+err.Error(),
1110+
)
1111+
return
1112+
}
1113+
1114+
err = waitForDomainState(r.client, domain, uint32(golibvirt.DomainShutoff), destroyOptions.ShutdownTimeout)
1115+
if err != nil {
1116+
resp.Diagnostics.AddError(
1117+
"Timeout Waiting for Domain Shutdown",
1118+
fmt.Sprintf("Domain did not reach shutoff state within %s: %s", destroyOptions.ShutdownTimeout, err),
1119+
)
1120+
return
1121+
}
1122+
} else {
1123+
err = r.client.Libvirt().DomainDestroyFlags(domain, destroyOptions.Flags)
1124+
if err != nil {
1125+
resp.Diagnostics.AddError(
1126+
"Failed to Destroy Domain",
1127+
"Failed to stop running domain: "+err.Error(),
1128+
)
1129+
return
1130+
}
10501131
}
10511132
}
10521133

internal/provider/domain_resource_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,38 @@ func TestAccDomainResource_running(t *testing.T) {
424424
})
425425
}
426426

427+
func TestAccDomainResource_destroyShutdownStoppedDomain(t *testing.T) {
428+
resource.Test(t, resource.TestCase{
429+
PreCheck: func() { testAccPreCheck(t) },
430+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
431+
CheckDestroy: testAccCheckDomainDestroy,
432+
Steps: []resource.TestStep{
433+
{
434+
Config: testAccDomainResourceConfigDestroyShutdownStopped("test-domain-destroy-shutdown-stopped", 1),
435+
Check: resource.ComposeAggregateTestCheckFunc(
436+
resource.TestCheckResourceAttr("libvirt_domain.test", "name", "test-domain-destroy-shutdown-stopped"),
437+
),
438+
},
439+
},
440+
})
441+
}
442+
443+
func TestAccDomainResource_destroyShutdownStoppedDomainDefaultTimeout(t *testing.T) {
444+
resource.Test(t, resource.TestCase{
445+
PreCheck: func() { testAccPreCheck(t) },
446+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
447+
CheckDestroy: testAccCheckDomainDestroy,
448+
Steps: []resource.TestStep{
449+
{
450+
Config: testAccDomainResourceConfigDestroyShutdownStoppedDefaultTimeout("test-domain-destroy-shutdown-stopped-default"),
451+
Check: resource.ComposeAggregateTestCheckFunc(
452+
resource.TestCheckResourceAttr("libvirt_domain.test", "name", "test-domain-destroy-shutdown-stopped-default"),
453+
),
454+
},
455+
},
456+
})
457+
}
458+
427459
func TestAccDomainResource_updateWithRunning(t *testing.T) {
428460
resource.Test(t, resource.TestCase{
429461
PreCheck: func() { testAccPreCheck(t) },
@@ -618,6 +650,54 @@ resource "libvirt_domain" "test" {
618650
`, name)
619651
}
620652

653+
func testAccDomainResourceConfigDestroyShutdownStopped(name string, timeout int64) string {
654+
return fmt.Sprintf(`
655+
656+
resource "libvirt_domain" "test" {
657+
name = %[1]q
658+
memory = 512
659+
memory_unit = "MiB"
660+
vcpu = 1
661+
type = "kvm"
662+
663+
destroy = {
664+
shutdown = {
665+
timeout = %[2]d
666+
}
667+
}
668+
669+
os = {
670+
type = "hvm"
671+
type_arch = "x86_64"
672+
type_machine = "q35"
673+
}
674+
}
675+
`, name, timeout)
676+
}
677+
678+
func testAccDomainResourceConfigDestroyShutdownStoppedDefaultTimeout(name string) string {
679+
return fmt.Sprintf(`
680+
681+
resource "libvirt_domain" "test" {
682+
name = %[1]q
683+
memory = 512
684+
memory_unit = "MiB"
685+
vcpu = 1
686+
type = "kvm"
687+
688+
destroy = {
689+
shutdown = {}
690+
}
691+
692+
os = {
693+
type = "hvm"
694+
type_arch = "x86_64"
695+
type_machine = "q35"
696+
}
697+
}
698+
`, name)
699+
}
700+
621701
func testAccCheckDomainIsRunning(name string) resource.TestCheckFunc {
622702
return func(s *terraform.State) error {
623703
ctx := context.Background()

0 commit comments

Comments
 (0)