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

Add xenstore support to the xenorchestra_vm resource #295

Merged
merged 4 commits into from
Feb 21, 2024
Merged
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
26 changes: 21 additions & 5 deletions client/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,12 @@ type Vm struct {
ResourceSet *FlatResourceSet `json:"resourceSet"`
// TODO: (#145) Uncomment this once issues with secure_boot have been figured out
// SecureBoot bool `json:"secureBoot,omitempty"`
Tags []string `json:"tags"`
Videoram Videoram `json:"videoram,omitempty"`
Vga string `json:"vga,omitempty"`
StartDelay int `json:startDelay,omitempty"`
Host string `json:"$container"`
Tags []string `json:"tags"`
Videoram Videoram `json:"videoram,omitempty"`
Vga string `json:"vga,omitempty"`
StartDelay int `json:startDelay,omitempty"`
Host string `json:"$container"`
XenstoreData map[string]interface{} `json:"xenStoreData,omitempty"`

// These fields are used for passing in disk inputs when
// creating Vms, however, this is not a real field as far
Expand Down Expand Up @@ -357,6 +358,17 @@ func (c *Client) CreateVm(vmReq Vm, createTime time.Duration) (*Vm, error) {
return nil, err
}

xsParams := map[string]interface{}{
"id": vmId,
"xenStoreData": vmReq.XenstoreData,
}
var success bool
err = c.Call("vm.set", xsParams, &success)

if err != nil {
return nil, err
}

bootAfterCreate := params["bootAfterCreate"].(bool)
if !bootAfterCreate && vmReq.PowerState == RunningPowerState {
err = c.StartVm(vmId)
Expand Down Expand Up @@ -431,6 +443,10 @@ func (c *Client) UpdateVm(vmReq Vm) (*Vm, error) {
params["resourceSet"] = vmReq.ResourceSet
}

if len(vmReq.XenstoreData) > 0 {
params["xenStoreData"] = vmReq.XenstoreData
}

vga := vmReq.Vga
if vga != "" {
params["vga"] = vga
Expand Down
1 change: 1 addition & 0 deletions docs/data-sources/vms.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Read-Only:
- `vga` (String)
- `videoram` (Number)
- `wait_for_ip` (Boolean)
- `xenstore` (Map of String)

<a id="nestedobjatt--vms--disk"></a>
### Nested Schema for `vms.disk`
Expand Down
9 changes: 6 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,14 @@ provider "xenorchestra" {
<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `password` (String) Password for xoa api. Can be set via the XOA_PASSWORD environment variable.
- `url` (String) Hostname of the xoa router. Can be set via the XOA_URL environment variable.
- `username` (String) User account for xoa api. Can be set via the XOA_USER environment variable.

### Optional

- `insecure` (Boolean) Whether SSL should be verified or not. Can be set via the XOA_INSECURE environment variable.
- `password` (String) Password for xoa api. Can be set via the XOA_PASSWORD environment variable.
- `retry_max_time` (String) If `retry_mode` is set, this specifies the duration for which the backoff method will continue retries. Can be set via the `XOA_RETRY_MAX_TIME` environment variable
- `retry_mode` (String) Specifies if retries should be attempted for requests that require eventual . Can be set via the XOA_RETRY_MODE environment variable.
- `url` (String) Hostname of the xoa router. Can be set via the XOA_URL environment variable.
- `username` (String) User account for xoa api. Can be set via the XOA_USER environment variable.
8 changes: 8 additions & 0 deletions docs/resources/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ resource "xenorchestra_vm" "bar" {
timeouts {
create = "20m"
}

// Note: Xen Orchestra populates values within Xenstore and will need ignored via
// lifecycle ignore_changes or modeled in your terraform code
xenstore = {
key1 = "val1"
key2 = "val2"
}
}

# vm resource that uses wait_for_ip
Expand Down Expand Up @@ -170,6 +177,7 @@ $ xo-cli xo.getAllObjects filter='json:{"id": "cf7b5d7d-3cd5-6b7c-5025-5c935c8cd
- `vga` (String) The video adapter the VM should use. Possible values include std and cirrus.
- `videoram` (Number) The videoram option the VM should use. Possible values include 1, 2, 4, 8, 16
- `wait_for_ip` (Boolean) Whether terraform should wait until IP addresses are present on the VM's network interfaces before considering it created. This only works if guest-tools are installed in the VM. Defaults to false.
- `xenstore` (Map of String) The key value pairs to be populated in xenstore.

### Read-Only

Expand Down
7 changes: 7 additions & 0 deletions examples/resources/xenorchestra_vm/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ resource "xenorchestra_vm" "bar" {
timeouts {
create = "20m"
}

// Note: Xen Orchestra populates values within Xenstore and will need ignored via
// lifecycle ignore_changes or modeled in your terraform code
xenstore = {
key1 = "val1"
key2 = "val2"
}
}

# vm resource that uses wait_for_ip
Expand Down
49 changes: 48 additions & 1 deletion xoa/resource_xenorchestra_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"regexp"
"sort"
"strconv"
"strings"
"time"

"github.com/ddelnano/terraform-provider-xenorchestra/client"
Expand Down Expand Up @@ -408,6 +409,14 @@ $ xo-cli xo.getAllObjects filter='json:{"id": "cf7b5d7d-3cd5-6b7c-5025-5c935c8cd
},
},
},
"xenstore": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
Description: "The key value pairs to be populated in xenstore.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"tags": resourceTags(),
}
}
Expand Down Expand Up @@ -566,7 +575,8 @@ func resourceVmCreate(d *schema.ResourceData, m interface{}) error {
Videoram: client.Videoram{
Value: d.Get("videoram").(int),
},
Vga: d.Get("vga").(string),
XenstoreData: d.Get("xenstore").(map[string]interface{}),
Vga: d.Get("vga").(string),
}

affinityHost := d.Get("affinity_host").(string)
Expand Down Expand Up @@ -927,6 +937,23 @@ func resourceVmUpdate(d *schema.ResourceData, m interface{}) error {
vmReq.AffinityHost = &affinityHost
}

if d.HasChange("xenstore") {
xenstoreParams := map[string]interface{}{}
o, n := d.GetChange("xenstore")
oXs := o.(map[string]interface{})
nXs := n.(map[string]interface{})

for k, _ := range oXs {
xenstoreParams[k] = nil
}

for k, v := range nXs {
xenstoreParams[k] = v
}

vmReq.XenstoreData = xenstoreParams
}

haltPerformed := false

if haltForUpdates {
Expand Down Expand Up @@ -1185,10 +1212,30 @@ func recordToData(resource client.Vm, vifs []client.VIF, disks []client.Disk, cd
return err
}
}
if xenstore := d.Get("xenstore").(map[string]interface{}); len(xenstore) > 0 {
filtered := filterXenstoreDataToVmData(resource.XenstoreData)
if err := d.Set("xenstore", filtered); err != nil {
return err
}
}

return nil
}

func filterXenstoreDataToVmData(xenstore map[string]interface{}) map[string]interface{} {
filtered := map[string]interface{}{}
for key, value := range xenstore {
if strings.HasPrefix(key, "vm-data/") {
pieces := strings.SplitAfterN(key, "vm-data/", 2)
if len(pieces) != 2 {
continue
}
filtered[pieces[1]] = value
}
}
return filtered
}

func vmBlockedOperationsToList(v client.Vm) []string {
blockedOperations := []string{}
for k, _ := range v.BlockedOperations {
Expand Down
117 changes: 117 additions & 0 deletions xoa/resource_xenorchestra_vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,46 @@ func TestAccXenorchestraVm_createWhenWaitingForIp(t *testing.T) {
})
}

func TestAccXenorchestraVm_createAndUpdateXenstoreData(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: testAccProviders,
CheckDestroy: testAccCheckXenorchestraVmDestroy,
Steps: []resource.TestStep{
{
Config: testAccVmConfigWithSingleXenstoreData(vmName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccVmExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "id"),
resource.TestCheckResourceAttr(resourceName, "xenstore.%", "2"),
resource.TestCheckResourceAttr(resourceName, "xenstore.first", "value"),
),
},
{
Config: testAccVmConfigWithMultipleXenstoreData(vmName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccVmExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "id"),
resource.TestCheckResourceAttr(resourceName, "xenstore.first", "value"),
resource.TestCheckResourceAttr(resourceName, "xenstore.second", "value"),
),
},
{
Config: testAccVmConfigWithSingleXenstoreData(vmName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccVmExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "id"),
resource.TestCheckResourceAttr(resourceName, "xenstore.%", "2"),
resource.TestCheckResourceAttr(resourceName, "xenstore.first", "value"),
resource.TestCheckNoResourceAttr(resourceName, "xenstore.second"),
),
},
},
})
}

func TestAccXenorchestraVm_ensureVmsInResourceSetsCanBeUpdatedByNonAdminUsers(t *testing.T) {
vmName := fmt.Sprintf("%s - %s", accTestPrefix, t.Name())
adminUser := os.Getenv("XOA_USER")
Expand Down Expand Up @@ -2261,6 +2301,83 @@ resource "xenorchestra_vm" "bar" {
`, accDefaultNetwork.NameLabel, accTestPool.Id, vmName, accDefaultSr.Id)
}

func testAccVmConfigWithSingleXenstoreData(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
wait_for_ip = true
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
}

xenstore = {
first = "value"
}

lifecycle {
ignore_changes = [
xenstore["mmio-hole-size"],
]
}
}
`, accDefaultNetwork.NameLabel, accTestPool.Id, vmName, accDefaultSr.Id)
}

func testAccVmConfigWithMultipleXenstoreData(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
wait_for_ip = true
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
}

xenstore = {
first = "value"
second = "value"
}

lifecycle {
ignore_changes = [
xenstore["mmio-hole-size"],
]
}
}
`, accDefaultNetwork.NameLabel, accTestPool.Id, vmName, accDefaultSr.Id)
}

func testAccVmConfigWithDiskNameLabelAndNameDescription(vmName, nameLabel, description string) string {
return testAccCloudConfigConfig(fmt.Sprintf("vm-template-%s", vmName), "template") + testAccTemplateConfig() + fmt.Sprintf(`
data "xenorchestra_network" "network" {
Expand Down
Loading