Skip to content

Commit

Permalink
Add xenstore support to the xenorchestra_vm resource (#295)
Browse files Browse the repository at this point in the history
* Add xenstore support to the xenorchestra_vm resource

Signed-off-by: Dom Del Nano <ddelnano@gmail.com>

* Regenerate vm resource and data source documentation

Signed-off-by: Dom Del Nano <ddelnano@gmail.com>

* Ensure that the xenstore key is omitted from state if it wasn't specified in the tf code

Signed-off-by: Dom Del Nano <ddelnano@gmail.com>

* Fix tf doc generation

Signed-off-by: Dom Del Nano <ddelnano@gmail.com>

---------

Signed-off-by: Dom Del Nano <ddelnano@gmail.com>
  • Loading branch information
ddelnano committed Feb 21, 2024
1 parent 67d73fa commit 31bec5a
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 9 deletions.
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

0 comments on commit 31bec5a

Please sign in to comment.