Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance xenorchestra_vm to provide a method for graceful termination #222

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/resources/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ $ xo-cli xo.getAllObjects filter='json:{"id": "cf7b5d7d-3cd5-6b7c-5025-5c935c8cd
* `vga` - (Optional) The video adapter the VM should use. Possible values include std and cirrus.
* `videoram` - (Optional) The videoram option the VM should use. Possible values include 1, 2, 4, 8, 16
* `start_delay` - (Optional) Number of seconds the VM should be delayed from starting
* `use_graceful_termination` - (Optional) If true the VM will be shutdown before it is terminated when destroyed. Defaults to false.

## Timeouts

Expand Down
47 changes: 47 additions & 0 deletions xoa/internal/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,50 @@ func GetFailToStartAndHaltXOClient(d *schema.ResourceData) (interface{}, error)
}
return newFailToStartAndHaltClient(config)
}

// gracefulVmTerminationClient is a mock client that verifies Vms are only terminated
// if they are stopped first. This is necessary to validate the xenorchestra_vm resource's
// graceful termination functionality works.
type gracefulVmTerminationClient struct {
*client.Client
}

func (c gracefulVmTerminationClient) DeleteVm(id string) error {
vm, err := c.GetVm(client.Vm{Id: id})

if err != nil {
return err
}

if vm.PowerState != "Halted" {
return errors.New("mock client did not receive a stopped Vm. Graceful termination was bypassed!\n")
}

return c.Client.DeleteVm(id)
}

func newGracefulVmTerminationClient(config client.Config) (client.XOClient, error) {
xoClient, err := client.NewClient(config)

if err != nil {
return nil, err
}

c := xoClient.(*client.Client)

return &gracefulVmTerminationClient{c}, nil
}

func GetGracefulVmTerminationClient(d *schema.ResourceData) (interface{}, error) {
url := d.Get("url").(string)
username := d.Get("username").(string)
password := d.Get("password").(string)
insecure := d.Get("insecure").(bool)
config := client.Config{
Url: url,
Username: username,
Password: password,
InsecureSkipVerify: insecure,
}
return newGracefulVmTerminationClient(config)
}
8 changes: 8 additions & 0 deletions xoa/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import (

var testAccProviders map[string]*schema.Provider
var testAccFailToStartAndHaltProviders map[string]*schema.Provider
var testAccGracefulVmTerminationProviders map[string]*schema.Provider

var testAccProvider *schema.Provider
var testAccFailToStartHaltVmProvider *schema.Provider
var testAccGracefulVmTerminationProvider *schema.Provider

func init() {
testAccProvider = Provider()
Expand All @@ -25,6 +27,12 @@ func init() {
testAccFailToStartAndHaltProviders = map[string]*schema.Provider{
"xenorchestra": testAccFailToStartHaltVmProvider,
}

testAccGracefulVmTerminationProvider = Provider()
testAccGracefulVmTerminationProvider.ConfigureFunc = internal.GetGracefulVmTerminationClient
testAccGracefulVmTerminationProviders = map[string]*schema.Provider{
"xenorchestra": testAccGracefulVmTerminationProvider,
}
}

func testAccPreCheck(t *testing.T) {
Expand Down
22 changes: 21 additions & 1 deletion xoa/resource_xenorchestra_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ func resourceVmSchema() map[string]*schema.Schema {
Default: 0,
Optional: true,
},
"use_graceful_termination": &schema.Schema{
Type: schema.TypeBool,
Default: false,
Optional: true,
},
// TODO: (#145) Uncomment this once issues with secure_boot have been figured out
// "secure_boot": &schema.Schema{
// Type: schema.TypeBool,
Expand Down Expand Up @@ -832,9 +837,24 @@ func resourceVmUpdate(d *schema.ResourceData, m interface{}) error {

func resourceVmDelete(d *schema.ResourceData, m interface{}) error {
c := m.(client.XOClient)
gracefulTermination := d.Get("use_graceful_termination").(bool)
vmId := d.Id()

err := c.DeleteVm(d.Id())
if gracefulTermination {
vm, err := c.GetVm(client.Vm{Id: vmId})
if err != nil {
return err
}

if vm.PowerState == "Running" {
err = c.HaltVm(vmId)
if err != nil {
return err
}
}
}

err := c.DeleteVm(vmId)
if err != nil {
return err
}
Expand Down
91 changes: 91 additions & 0 deletions xoa/resource_xenorchestra_vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,68 @@ func TestAccXenorchestraVm_createWithMutipleDisks(t *testing.T) {
})
}

func TestAccXenorchestraVm_gracefulTermination(t *testing.T) {
resourceName := "xenorchestra_vm.bar"
vmName := fmt.Sprintf("%s - %s", accTestPrefix, t.Name())
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccGracefulVmTerminationProviders,
CheckDestroy: testAccCheckXenorchestraVmDestroy,
Steps: []resource.TestStep{
{
Config: testAccVmConfigWithGracefulTermination(vmName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccVmExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "id")),
},
{
Config: testAccVmConfigWithGracefulTermination(vmName),
Destroy: true,
},
},
})
}

// Reference: https://github.com/terra-farm/terraform-provider-xenorchestra/pull/212
func TestAccXenorchestraVm_gracefulTerminationForShutdownVm(t *testing.T) {
resourceName := "xenorchestra_vm.bar"
vmName := fmt.Sprintf("%s - %s", accTestPrefix, t.Name())
shutdownVm := func() {
c, err := client.NewClient(client.GetConfigFromEnv())
if err != nil {
t.Fatalf("failed to create client with error: %v", err)
}

vm, err := c.GetVm(client.Vm{
NameLabel: vmName,
})

err = c.HaltVm(vm.Id)

if err != nil {
t.Fatalf("failed to halt VM with error: %v", err)
}
}
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccGracefulVmTerminationProviders,
CheckDestroy: testAccCheckXenorchestraVmDestroy,
Steps: []resource.TestStep{
{
Config: testAccVmConfigWithGracefulTermination(vmName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccVmExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "id")),
},
{
PreConfig: shutdownVm,
Config: testAccVmConfig(vmName),
Destroy: true,
},
},
})
}

func TestAccXenorchestraVm_addAndRemoveDisksToVm(t *testing.T) {
resourceName := "xenorchestra_vm.bar"
vmName := fmt.Sprintf("%s - %s", accTestPrefix, t.Name())
Expand Down Expand Up @@ -1666,6 +1728,35 @@ resource "xenorchestra_vm" "bar" {
`, accDefaultNetwork.NameLabel, accTestPool.Id, vmName, accDefaultSr.Id)
}

func testAccVmConfigWithGracefulTermination(vmName string) string {
return testAccCloudConfigConfig(fmt.Sprintf("vm-template-%s", vmName), "template") + testAccTemplateConfig() + fmt.Sprintf(`
data "xenorchestra_network" "network" {
name_label = "%s"
pool_id = "%s"
}

resource "xenorchestra_vm" "bar" {
memory_max = 4295000000
cpus = 1
cloud_config = "${xenorchestra_cloud_config.bar.template}"
name_label = "%s"
name_description = "description"
template = "${data.xenorchestra_template.template.id}"
network {
network_id = "${data.xenorchestra_network.network.id}"
}

disk {
sr_id = "%s"
name_label = "disk 1"
size = 10001317888
}

use_graceful_termination = true
}
`, accDefaultNetwork.NameLabel, accTestPool.Id, vmName, accDefaultSr.Id)
}

func testAccVmConfigPXEBoot(vmName string) string {
return testAccCloudConfigConfig(fmt.Sprintf("vm-template-%s", vmName), "template") + testAccNonDefaultTemplateConfig(disklessTestTemplate.NameLabel) + fmt.Sprintf(`
data "xenorchestra_network" "network" {
Expand Down