diff --git a/nutanix/resource_nutanix_virtual_machine.go b/nutanix/resource_nutanix_virtual_machine.go index b1dec4686..a27c344cc 100644 --- a/nutanix/resource_nutanix_virtual_machine.go +++ b/nutanix/resource_nutanix_virtual_machine.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/spf13/cast" v3 "github.com/terraform-providers/terraform-provider-nutanix/client/v3" "github.com/terraform-providers/terraform-provider-nutanix/utils" @@ -662,6 +663,46 @@ func resourceNutanixVirtualMachine() *schema.Resource { Optional: true, Computed: true, }, + "storage_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "flash_mode": { + Type: schema.TypeString, + Optional: true, + }, + "storage_container_reference": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "url": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "kind": { + Type: schema.TypeString, + Optional: true, + Default: "storage_container", + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, "device_properties": { Type: schema.TypeList, Optional: true, @@ -1694,59 +1735,49 @@ func expandDiskList(d *schema.ResourceData, isCreation bool) ([]*v3.VMDisk, erro dsk := v.([]interface{}) if len(dsk) > 0 { dls := make([]*v3.VMDisk, len(dsk)) + for k, val := range dsk { hasDSRef := false v := val.(map[string]interface{}) dl := &v3.VMDisk{} + + // uuid if v1, ok1 := v["uuid"]; ok1 && v1.(string) != "" { dl.UUID = utils.StringPtr(v1.(string)) } + // storage_config + if v, ok1 := v["storage_config"]; ok1 { + dl.StorageConfig = expandStorageConfig(v.([]interface{})) + } + // device_properties if v1, ok1 := v["device_properties"]; ok1 { - dvp := v1.([]interface{}) - if len(dvp) > 0 { - d := dvp[0].(map[string]interface{}) - dp := &v3.VMDiskDeviceProperties{} - if v1, ok := d["device_type"]; ok { - dp.DeviceType = utils.StringPtr(v1.(string)) - } - if v2, ok := d["disk_address"]; ok && len(v2.(map[string]interface{})) > 0 { - da := v2.(map[string]interface{}) - v3disk := &v3.DiskAddress{} - if di, diok := da["device_index"]; diok { - index, _ := strconv.Atoi(di.(string)) - v3disk.DeviceIndex = utils.Int64Ptr(int64(index)) - } - if di, diok := da["adapter_type"]; diok { - v3disk.AdapterType = utils.StringPtr(di.(string)) - } - dp.DiskAddress = v3disk - } - dl.DeviceProperties = dp - } + dl.DeviceProperties = expandDeviceProperties(v1.([]interface{})) } + // data_source_reference if v1, ok := v["data_source_reference"]; ok && len(v1.(map[string]interface{})) != 0 { hasDSRef = true dsref := v1.(map[string]interface{}) dl.DataSourceReference = validateShortRef(dsref) } + // volume_group_reference if v1, ok := v["volume_group_reference"]; ok { volgr := v1.(map[string]interface{}) dl.VolumeGroupReference = validateRef(volgr) } - + // disk_size_bytes if v1, ok1 := v["disk_size_bytes"]; ok1 && v1.(int) != 0 { if hasDSRef && isCreation { return nil, fmt.Errorf(`"disk_list.%[1]d.disk_size_bytes": conflicts with disk_list.%[1]d.data_source_reference`, k) } dl.DiskSizeBytes = utils.Int64Ptr(int64(v1.(int))) } + // disk_size_mib if v1, ok := v["disk_size_mib"]; ok && v1.(int) != 0 { if hasDSRef && isCreation { return nil, fmt.Errorf(`"disk_list.%[1]d.disk_size_mib": conflicts with disk_list.%[1]d.data_source_reference`, k) } dl.DiskSizeMib = utils.Int64Ptr(int64(v1.(int))) } - dls[k] = dl } return dls, nil @@ -1755,6 +1786,48 @@ func expandDiskList(d *schema.ResourceData, isCreation bool) ([]*v3.VMDisk, erro return nil, nil } +func expandStorageConfig(storageConfig []interface{}) *v3.VMStorageConfig { + if len(storageConfig) > 0 { + v := storageConfig[0].(map[string]interface{}) + scr := v["storage_container_reference"].([]interface{})[0].(map[string]interface{}) + + return &v3.VMStorageConfig{ + FlashMode: cast.ToString(v["flash_mode"]), + StorageContainerReference: &v3.StorageContainerReference{ + URL: cast.ToString(scr["url"]), + Kind: cast.ToString(scr["kind"]), + UUID: cast.ToString(scr["uuid"]), + }, + } + } + return nil +} + +func expandDeviceProperties(deviceProperties []interface{}) *v3.VMDiskDeviceProperties { + if len(deviceProperties) > 0 { + dp := &v3.VMDiskDeviceProperties{} + d := deviceProperties[0].(map[string]interface{}) + + if v, ok := d["device_type"]; ok { + dp.DeviceType = utils.StringPtr(v.(string)) + } + if v, ok := d["disk_address"]; ok && len(v.(map[string]interface{})) > 0 { + da := v.(map[string]interface{}) + v3disk := &v3.DiskAddress{} + + if di, diOk := da["device_index"]; diOk { + v3disk.DeviceIndex = utils.Int64Ptr(cast.ToInt64(di)) + } + if at, atOk := da["adapter_type"]; atOk { + v3disk.AdapterType = utils.StringPtr(at.(string)) + } + dp.DiskAddress = v3disk + } + return dp + } + return nil +} + func expandSerialPortList(d *schema.ResourceData) []*v3.VMSerialPort { if v, ok := d.GetOk("serial_port_list"); ok { spl := v.([]interface{}) diff --git a/nutanix/resource_nutanix_virtual_machine_test.go b/nutanix/resource_nutanix_virtual_machine_test.go index 0f0ecf647..afbd10369 100644 --- a/nutanix/resource_nutanix_virtual_machine_test.go +++ b/nutanix/resource_nutanix_virtual_machine_test.go @@ -362,6 +362,25 @@ func TestAccNutanixVirtualMachine_cloningVM(t *testing.T) { }) } +func TestAccNutanixVirtualMachine_withDiskContainer(t *testing.T) { + r := acctest.RandInt() + + resourceName := "nutanix_virtual_machine.vm-disk" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNutanixVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccNutanixVMConfigWithDiskContainer(r), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "disk_list.#"), + resource.TestCheckResourceAttr(resourceName, "disk_list.#", "1"), + ), + }, + }}) +} + func testAccCheckNutanixVirtualMachineExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -927,3 +946,36 @@ func testAccNutanixVMConfigHotAdd(vmName string, cpus, sockets, memory int, hotA } `, vmName, cpus, sockets, memory, hotAdd, imageName) } + +func testAccNutanixVMConfigWithDiskContainer(r int) string { + return fmt.Sprintf(` + data "nutanix_clusters" "clusters" {} + + locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] + } + + resource "nutanix_virtual_machine" "vm-disk" { + name = "test-dou-vm-%[1]d" + cluster_uuid = local.cluster1 + num_vcpus_per_socket = 1 + num_sockets = 1 + memory_size_mib = 186 + + disk_list { + disk_size_bytes = 68157440 + disk_size_mib = 65 + + storage_config { + storage_container_reference { + kind = "storage_container" + uuid = "2bbe77bc-fd14-4697-8de1-6369757f9219" + } + } + } + } + `, r) +} diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index 4a41fd123..c40adc030 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -31,6 +31,31 @@ resource "nutanix_virtual_machine" "vm1" { } ``` +## Example Usage with storage config +```hcl +data "nutanix_clusters" "clusters" {} + +resource "nutanix_virtual_machine" "vm" { + name = "myVm" + cluster_uuid = data.nutanix_clusters.clusters.entities.0.metadata.uuid + num_vcpus_per_socket = 1 + num_sockets = 1 + memory_size_mib = 186 + + disk_list { + disk_size_bytes = 68157440 + disk_size_mib = 65 + + storage_config { + storage_container_reference { + kind = "storage_container" + uuid = "2bbe67bc-fd14-4637-8de1-6379257f4219" + } + } + } +} +``` + ## Argument Reference The following arguments are supported: @@ -92,6 +117,17 @@ The device_properties attribute supports the following. * `device_type`: - A Disk type (default: DISK). * `disk_address`: - Address of disk to boot from. +### Storage Config +User inputs of storage configuration parameters for VMs. + +* `flash_mode`: - State of the storage policy to pin virtual disks to the hot tier. When specified as a VM attribute, the storage policy applies to all virtual disks of the VM unless overridden by the same attribute specified for a virtual disk. + +* `storage_container_reference`: - Reference to a kind. Either one of (kind, uuid) or url needs to be specified. +* `storage_container_reference.#.url`: - GET query on the URL will provide information on the source. +* `storage_container_reference.#.kind`: - kind of the container reference +* `storage_container_reference.#.name`: - name of the container reference +* `storage_container_reference.#.uuid`: - uiid of the container reference + ### Sysprep The guest_customization_sysprep attribute supports the following: