diff --git a/GNUmakefile b/GNUmakefile index 469ae4167..5b3f06664 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -17,9 +17,9 @@ testacc: fmtcheck fmt: @echo "==> Fixing source code with gofmt..." - goimports -s -w ./$(PKG_NAME) - goimports -s -w ./client - goimports -s -w ./utils + goimports -w ./$(PKG_NAME) + goimports -w ./client + goimports -w ./utils fmtcheck: @sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'" diff --git a/client/v3/v3_structs.go b/client/v3/v3_structs.go index 2590acb07..12a35509c 100644 --- a/client/v3/v3_structs.go +++ b/client/v3/v3_structs.go @@ -57,6 +57,8 @@ type VMNic struct { // The NIC's UUID, which is used to uniquely identify this particular NIC. This UUID may be used to refer to the NIC // outside the context of the particular VM it is attached to. UUID *string `json:"uuid,omitempty"` + + IsConnected *bool `json:"is_connected,omitempty"` } // DiskAddress Disk Address. @@ -344,6 +346,8 @@ type VMNicOutputStatus struct { // The NIC's UUID, which is used to uniquely identify this particular NIC. This UUID may be used to refer to the NIC // outside the context of the particular VM it is attached to. UUID *string `json:"uuid,omitempty"` + + IsConnected *bool `json:"is_connected,omitempty"` } // NutanixGuestToolsStatus Information regarding Nutanix Guest Tools. diff --git a/examples/main.tf b/examples/main.tf index ba0146cad..3c8e316e2 100644 --- a/examples/main.tf +++ b/examples/main.tf @@ -11,7 +11,7 @@ # - nutanix_subnet # - nutanix_image # - data sources -# - +# - nutanix_clusters # - # - # - script Variables @@ -27,13 +27,15 @@ ### While it may be possible to use Prism Element directly, Nutanix's ### provider is not structured or tested for this. Using Prism Central will ### give the broadest capabilities across the board -provider "nutanix" { +/* provider "nutanix" { username = "admin" password = "Nutanix/1234" endpoint = "10.5.80.255" insecure = true port = 9440 -} +} */ + +data "nutanix_clusters" "clusters" {} ### Define Script Local Variables ### This can be used for any manner of things, but is useful for like clusterid, @@ -41,7 +43,7 @@ provider "nutanix" { ### TODO: Need to make clusters a data source object, such that consumers do ### not need to manually provision cluster ID locals { - cluster1 = "00054051-250f-5ccc-0000-00000000cf0d" + cluster1 = "${data.nutanix_clusters.clusters.entities.1.metadata.uuid}" } ########################## @@ -97,9 +99,11 @@ locals { # called in as data sources, which you can see in the data sources section # above. resource "nutanix_image" "cirros-034-disk" { - name = "cirros-034-disk" - source_uri = "http://endor.dyn.nutanix.com/acro_images/DISKs/cirros-0.3.4-x86_64-disk.img" - description = "heres a tiny linux image, not an iso, but a real disk!" + name = "cirros-034-disk" + + #source_uri = "http://endor.dyn.nutanix.com/acro_images/DISKs/cirros-0.3.4-x86_64-disk.img" + source_uri = "http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-disk.img" + description = "heres a tiny linux image, not an iso, but a real disk!" } ### Subnet Resources (Virtual Networks within AHV) @@ -143,28 +147,33 @@ resource "nutanix_image" "cirros-034-disk" { # ### Define Terraform Managed Subnets resource "nutanix_subnet" "infra-managed-network-140" { # What cluster will this VLAN live on? - cluster_reference = { - kind = "cluster" - uuid = "${local.cluster1}" - } + cluster_uuid = "${local.cluster1}" # General Information name = "infra-managed-network-140" vlan_id = 140 subnet_type = "VLAN" -# # Provision a Managed L3 Network -# # This bit is only needed if you intend to turn on AHV's IPAM -# subnet_ip = "10.250.140.0" -# default_gateway_ip = "10.250.140.1" -# prefix_length = 24 -# dhcp_options { -# boot_file_name = "bootfile" -# domain_name = "nutanix" -# domain_name_server_list = ["8.8.8.8", "4.2.2.2"] -# domain_search_list = ["terraform.nutanix.com", "terraform.unit.test.com"] -# tftp_server_name = "10.250.140.200" -# } + # Provision a Managed L3 Network + # This bit is only needed if you intend to turn on AHV's IPAM + subnet_ip = "172.21.32.0" + + default_gateway_ip = "172.21.32.1" + prefix_length = 24 + + dhcp_options { + boot_file_name = "bootfile" + domain_name = "ntnxlab" + tftp_server_name = "172.21.32.200" + } + + dhcp_server_address { + ip = "172.21.32.254" + } + + dhcp_domain_name_server_list = ["172.21.30.223"] + dhcp_domain_search_list = ["ntnxlab.local"] + #ip_config_pool_list_ranges = ["172.21.32.3 172.21.32.253"] } ### Virtual Machine Resources @@ -186,23 +195,18 @@ resource "nutanix_virtual_machine" "demo-01-web" { memory_size_mib = 4096 # What cluster will this VLAN live on? - cluster_reference = { - kind = "cluster" - uuid = "${local.cluster1}" - } + cluster_uuid = "${local.cluster1}" # What networks will this be attached to? nic_list = [{ # subnet_reference is saying, which VLAN/network do you want to attach here? - subnet_reference = { - kind = "subnet" - uuid = "${nutanix_subnet.infra-managed-network-140.id}" - } - - # ip_endpoint_list = { - # ip = "${local.ip_haproxy}" - # type = "ASSIGNED" - # } + subnet_uuid = "${nutanix_subnet.infra-managed-network-140.id}" + + # Used to set static IP. + # ip_endpoint_list = { + # ip = "172.21.32.20" + # type = "ASSIGNED" + # } }] # What disk/cdrom configuration will this have? @@ -215,11 +219,41 @@ resource "nutanix_virtual_machine" "demo-01-web" { uuid = "${nutanix_image.cirros-034-disk.id}" }] - # defining an additional entry in the disk_list array will create another - # disk in addition to the image we're showing off above. device_properties = [{ + disk_address { + device_index = 0 + adapter_type = "SCSI" + } + device_type = "DISK" }] - disk_size_mib = 5000 - }] + }, + { + # defining an additional entry in the disk_list array will create another. + + #disk_size_mib and disk_size_bytes must be set together. + disk_size_mib = 100000 + disk_size_bytes = 104857600000 + }, + ] + + #Using provisioners + #Use as the following provisioner block if you know that you are geeting an reachable IP address. + #Get ssh connection and execute commands. + # provisioner "remote-exec" { + # connection { + # user = "cirros" # user from the image attached + # password = "cubswin:)" #password from the user + # #host = "172.21.32.20" #Set if you know + # } + + # inline = [ + # "echo \"Hello World\"", + # ] + # } } + +# Show IP address + output "ip_address" { + value = "${lookup(nutanix_virtual_machine.demo-01-web.nic_list_status.0.ip_endpoint_list[0], "ip")}" + } diff --git a/go.sum b/go.sum index 6794af505..09b058aca 100644 --- a/go.sum +++ b/go.sum @@ -171,6 +171,7 @@ github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSW github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.1.1-0.20171002171727-8ebdfab36c66/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= diff --git a/nutanix/data_source_nutanix_virtual_machine.go b/nutanix/data_source_nutanix_virtual_machine.go index 12ddb3654..b198d7bc6 100644 --- a/nutanix/data_source_nutanix_virtual_machine.go +++ b/nutanix/data_source_nutanix_virtual_machine.go @@ -520,7 +520,7 @@ func dataSourceNutanixVirtualMachine() *schema.Resource { Computed: true, }, "disk_address": { - Type: schema.TypeList, + Type: schema.TypeMap, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -582,7 +582,6 @@ func dataSourceNutanixVirtualMachine() *schema.Resource { }, }, }, - "serial_port_list": { Type: schema.TypeList, Computed: true, diff --git a/nutanix/data_source_nutanix_virtual_machine_test.go b/nutanix/data_source_nutanix_virtual_machine_test.go index 8b26281c8..ede01fb8a 100644 --- a/nutanix/data_source_nutanix_virtual_machine_test.go +++ b/nutanix/data_source_nutanix_virtual_machine_test.go @@ -27,6 +27,25 @@ func TestAccNutanixVirtualMachineDataSource_basic(t *testing.T) { }) } +func TestAccNutanixVirtualMachineDataSource_WithDisk(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVMDataSourceConfigWithDisk(acctest.RandIntRange(0, 500)), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.nutanix_virtual_machine.nutanix_virtual_machine", "num_vcpus_per_socket", "1"), + resource.TestCheckResourceAttr( + "data.nutanix_virtual_machine.nutanix_virtual_machine", "num_sockets", "1"), + ), + }, + }, + }) +} + func testAccVMDataSourceConfig(r int) string { return fmt.Sprintf(` data "nutanix_clusters" "clusters" {} @@ -49,3 +68,62 @@ data "nutanix_virtual_machine" "nutanix_virtual_machine" { } `, r) } + +func testAccVMDataSourceConfigWithDisk(r int) string { + return fmt.Sprintf(` + data "nutanix_clusters" "clusters" {} + + locals { + cluster1 = "${data.nutanix_clusters.clusters.entities.0.service_list.0 == "PRISM_CENTRAL" + ? data.nutanix_clusters.clusters.entities.1.metadata.uuid : data.nutanix_clusters.clusters.entities.0.metadata.uuid}" + } + + resource "nutanix_image" "cirros-034-disk" { + name = "test-image-dou-vm-create-%[1]d" + source_uri = "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img" + description = "heres a tiny linux image, not an iso, but a real disk!" + } + + resource "nutanix_virtual_machine" "vm1" { + name = "test-dou-vm-%[1]d" + cluster_uuid = "${local.cluster1}" + num_vcpus_per_socket = 1 + num_sockets = 1 + memory_size_mib = 186 + + disk_list = [{ + # data_source_reference in the Nutanix API refers to where the source for + # the disk device will come from. Could be a clone of a different VM or a + # image like we're doing here + data_source_reference = [{ + kind = "image" + uuid = "${nutanix_image.cirros-034-disk.id}" + }] + + device_properties = [{ + disk_address { + device_index = 0, + adapter_type = "SCSI" + } + device_type = "DISK" + }] + #disk_size_bytes = 42950144 + #disk_size_mib = 41 + }, + { + disk_size_mib = 100 + }, + { + disk_size_mib = 200 + }, + { + disk_size_mib = 300 + }] + } + + data "nutanix_virtual_machine" "nutanix_virtual_machine" { + vm_id = "${nutanix_virtual_machine.vm1.id}" + } + + `, r) +} diff --git a/nutanix/resource_nutanix_image.go b/nutanix/resource_nutanix_image.go index 42233b34a..b4b038d74 100644 --- a/nutanix/resource_nutanix_image.go +++ b/nutanix/resource_nutanix_image.go @@ -306,7 +306,12 @@ func resourceNutanixImageCreate(d *schema.ResourceData, meta interface{}) error } if _, errw := stateConf.WaitForState(); errw != nil { - return fmt.Errorf("error waiting for image (%s) to create: %s", d.Id(), errw) + delErr := resourceNutanixImageDelete(d, meta) + if delErr != nil { + return fmt.Errorf("error waiting for image (%s) to delete in creation: %s", d.Id(), delErr) + } + d.SetId("") + return fmt.Errorf("error waiting for image (%s) to create: %s", UUID, errw) } // if we need to upload an image, we do it now @@ -502,7 +507,13 @@ func resourceNutanixImageUpdate(d *schema.ResourceData, meta interface{}) error } if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("error waiting for image (%s) to update: %s", d.Id(), err) + delErr := resourceNutanixImageDelete(d, meta) + if delErr != nil { + return fmt.Errorf("error waiting for image (%s) to delete in update: %s", d.Id(), delErr) + } + uuid := d.Id() + d.SetId("") + return fmt.Errorf("error waiting for image (%s) to update: %s", uuid, err) } return resourceNutanixImageRead(d, meta) @@ -542,6 +553,7 @@ func resourceNutanixImageDelete(d *schema.ResourceData, meta interface{}) error } if _, err := stateConf.WaitForState(); err != nil { + d.SetId("") return fmt.Errorf("error waiting for image (%s) to delete: %s", d.Id(), err) } diff --git a/nutanix/resource_nutanix_subnet.go b/nutanix/resource_nutanix_subnet.go index 314e683cd..c14f059cf 100644 --- a/nutanix/resource_nutanix_subnet.go +++ b/nutanix/resource_nutanix_subnet.go @@ -380,6 +380,11 @@ func resourceNutanixSubnetRead(d *schema.ResourceData, meta interface{}) error { d.SetId("") return nil } + errDel := resourceNutanixSubnetDelete(d, meta) + if errDel != nil { + return fmt.Errorf("error deleting subnet (%s) after read error: %+v", id, errDel) + } + d.SetId("") return fmt.Errorf("error reading subnet id (%s): %+v", id, err) } diff --git a/nutanix/resource_nutanix_virtual_machine.go b/nutanix/resource_nutanix_virtual_machine.go index 5268e895b..a5dcad3bc 100644 --- a/nutanix/resource_nutanix_virtual_machine.go +++ b/nutanix/resource_nutanix_virtual_machine.go @@ -254,6 +254,10 @@ func resourceNutanixVirtualMachine() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "is_connected": { + Type: schema.TypeString, + Computed: true, + }, }, }, }, @@ -346,6 +350,11 @@ func resourceNutanixVirtualMachine() *schema.Resource { Optional: true, Computed: true, }, + "is_connected": { + Type: schema.TypeString, + Optional: true, + Default: "true", + }, }, }, }, @@ -609,52 +618,50 @@ func resourceNutanixVirtualMachine() *schema.Resource { "disk_list": { Type: schema.TypeList, Optional: true, - ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "uuid": { Type: schema.TypeString, Optional: true, - ForceNew: true, + Computed: true, }, "disk_size_bytes": { Type: schema.TypeInt, Optional: true, - ForceNew: true, + Computed: true, }, "disk_size_mib": { Type: schema.TypeInt, Optional: true, - ForceNew: true, + Computed: true, }, "device_properties": { Type: schema.TypeList, Optional: true, - ForceNew: true, - + Computed: true, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "device_type": { Type: schema.TypeString, Optional: true, - ForceNew: true, + Computed: true, }, "disk_address": { - Type: schema.TypeList, + Type: schema.TypeMap, Optional: true, - ForceNew: true, - + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "device_index": { Type: schema.TypeInt, Optional: true, - ForceNew: true, + Computed: true, }, "adapter_type": { Type: schema.TypeString, Optional: true, - ForceNew: true, + Computed: true, }, }, }, @@ -663,47 +670,45 @@ func resourceNutanixVirtualMachine() *schema.Resource { }, }, "data_source_reference": { - Type: schema.TypeList, + Type: schema.TypeMap, Optional: true, - ForceNew: true, - + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "kind": { Type: schema.TypeString, Optional: true, - ForceNew: true, + Computed: true, }, "uuid": { Type: schema.TypeString, Optional: true, - ForceNew: true, + Computed: true, }, }, }, }, - "volume_group_reference": { - Type: schema.TypeList, + Type: schema.TypeMap, Optional: true, - ForceNew: true, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "kind": { Type: schema.TypeString, Optional: true, - ForceNew: true, + Computed: true, }, "name": { Type: schema.TypeString, Optional: true, - ForceNew: true, + Computed: true, }, "uuid": { Type: schema.TypeString, Optional: true, - ForceNew: true, + Computed: true, }, }, }, @@ -766,7 +771,9 @@ func resourceNutanixVirtualMachineCreate(d *schema.ResourceData, meta interface{ spec.ClusterReference = buildReference(clusterUUID.(string), "cluster") } - getVMResources(d, res) + if err := getVMResources(d, res); err != nil { + return err + } spec.Name = utils.StringPtr(n.(string)) spec.Resources = res @@ -792,8 +799,8 @@ func resourceNutanixVirtualMachineCreate(d *schema.ResourceData, meta interface{ MinTimeout: 3 * time.Second, } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("error waiting for vm (%s) to create: %s", d.Id(), err) + if _, errWaitTask := stateConf.WaitForState(); errWaitTask != nil { + return fmt.Errorf("error waiting for vm (%s) to create: %s", d.Id(), errWaitTask) } //Wait for IP available @@ -806,8 +813,18 @@ func resourceNutanixVirtualMachineCreate(d *schema.ResourceData, meta interface{ MinTimeout: 3 * time.Second, } - if _, err := waitIPConf.WaitForState(); err != nil { + vmIntentResponse, err := waitIPConf.WaitForState() + if err != nil { log.Printf("[WARN] could not get the IP for VM(%s): %s", uuid, err) + } else { + vm := vmIntentResponse.(*v3.VMIntentResponse) + + if len(vm.Status.Resources.NicList) > 0 && len(vm.Status.Resources.NicList[0].IPEndpointList) != 0 { + d.SetConnInfo(map[string]string{ + "type": "ssh", + "host": *vm.Status.Resources.NicList[0].IPEndpointList[0].IP, + }) + } } // Set terraform state id @@ -855,7 +872,11 @@ func resourceNutanixVirtualMachineRead(d *schema.ResourceData, meta interface{}) return fmt.Errorf("error setting nic_list for Virtual Machine %s: %s", d.Id(), err) } if err := d.Set("nic_list_status", flattenNicListStatus(resp.Status.Resources.NicList)); err != nil { - return fmt.Errorf("error setting nic_list for Virtual Machine %s: %s", d.Id(), err) + return fmt.Errorf("error setting nic_list_status for Virtual Machine %s: %s", d.Id(), err) + } + + if err := d.Set("disk_list", flattenDiskList(resp.Spec.Resources.DiskList)); err != nil { + return fmt.Errorf("error setting disk_list for Virtual Machine %s: %s", d.Id(), err) } if err := d.Set("serial_port_list", flattenSerialPortList(resp.Status.Resources.SerialPortList)); err != nil { @@ -1127,6 +1148,12 @@ func resourceNutanixVirtualMachineUpdate(d *schema.ResourceData, meta interface{ res.NicList = expandNicList(d) } + if d.HasChange("disk_list") { + if res.DiskList, err = expandDiskList(d, false); err != nil { + return err + } + } + if d.HasChange("serial_port_list") { res.SerialPortList = expandSerialPortList(d) } @@ -1363,7 +1390,7 @@ func resourceNutanixVirtualMachineExists(d *schema.ResourceData, meta interface{ return false, nil } -func getVMResources(d *schema.ResourceData, vm *v3.VMResources) { +func getVMResources(d *schema.ResourceData, vm *v3.VMResources) error { vm.PowerState = utils.StringPtr("ON") if v, ok := d.GetOk("num_vnuma_nodes"); ok { @@ -1479,9 +1506,13 @@ func getVMResources(d *schema.ResourceData, vm *v3.VMResources) { if v, ok := d.GetOk("enable_script_exec"); ok { vm.PowerStateMechanism.GuestTransitionConfig.EnableScriptExec = utils.BoolPtr(v.(bool)) } - - vm.DiskList = expandDiskList(d) vm.SerialPortList = expandSerialPortList(d) + + vmDiskList, err := expandDiskList(d, true) + + vm.DiskList = vmDiskList + + return err } func expandNicList(d *schema.ResourceData) []*v3.VMNic { @@ -1519,6 +1550,12 @@ func expandNicList(d *schema.ResourceData) []*v3.VMNic { v := value.(string) nic.SubnetReference = buildReference(v, "subnet") } + if value, ok := val["is_connected"]; ok { + v := value.(string) + IsConnected, _ := strconv.ParseBool(v) + nic.IsConnected = utils.BoolPtr(IsConnected) + + } nics = append(nics, nic) } return nics @@ -1547,24 +1584,18 @@ func expandIPAddressList(ipl []interface{}) []*v3.IPAddress { return nil } -func expandDiskList(d *schema.ResourceData) []*v3.VMDisk { +func expandDiskList(d *schema.ResourceData, isCreation bool) ([]*v3.VMDisk, error) { if v, ok := d.GetOk("disk_list"); ok { 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{} if v1, ok1 := v["uuid"]; ok1 && v1.(string) != "" { dl.UUID = utils.StringPtr(v1.(string)) } - if v1, ok1 := v["disk_size_bytes"]; ok1 && v1.(int) != 0 { - dl.DiskSizeBytes = utils.Int64Ptr(int64(v1.(int))) - } - if v1, ok := v["disk_size_mib"]; ok && v1.(int) != 0 { - dl.DiskSizeMib = utils.Int64Ptr(int64(v1.(int))) - } if v1, ok1 := v["device_properties"]; ok1 { dvp := v1.([]interface{}) if len(dvp) > 0 { @@ -1574,41 +1605,53 @@ func expandDiskList(d *schema.ResourceData) []*v3.VMDisk { dp.DeviceType = utils.StringPtr(v1.(string)) } if v2, ok := d["disk_address"]; ok { - if len(v2.([]interface{})) > 0 { - da := v2.([]interface{})[0].(map[string]interface{}) - v3disk := &v3.DiskAddress{} - if di, diok := da["device_index"]; diok { - v3disk.DeviceIndex = utils.Int64Ptr(int64(di.(int))) - } - if di, diok := da["adapter_type"]; diok { - v3disk.AdapterType = utils.StringPtr(di.(string)) - } - dp.DiskAddress = v3disk + 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 } } - if v1, ok := v["data_source_reference"]; ok { - dsref := v1.([]interface{}) - if len(dsref) > 0 { - dsri := dsref[0].(map[string]interface{}) - dl.DataSourceReference = validateShortRef(dsri) - } + if v1, ok := v["data_source_reference"]; ok && len(v1.(map[string]interface{})) != 0 { + fmt.Printf("%+v\n", v1) + hasDSRef = true + dsref := v1.(map[string]interface{}) + fmt.Printf("len(%d)\n", len(dsref)) + dl.DataSourceReference = validateShortRef(dsref) + } if v1, ok := v["volume_group_reference"]; ok { - volgr := v1.([]interface{}) - if len(volgr) > 0 { - dsri := volgr[0].(map[string]interface{}) - dl.VolumeGroupReference = validateRef(dsri) + volgr := v1.(map[string]interface{}) + dl.VolumeGroupReference = validateRef(volgr) + } + + 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))) + } + 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 + return dls, nil } } - return nil + return nil, nil } func expandSerialPortList(d *schema.ResourceData) []*v3.VMSerialPort { @@ -1705,6 +1748,7 @@ func preFillResUpdateRequest(res *v3.VMResources, response *v3.VMIntentResponse) res.NumVcpusPerSocket = response.Status.Resources.NumVcpusPerSocket res.VgaConsoleEnabled = response.Status.Resources.VgaConsoleEnabled res.HardwareClockTimezone = response.Status.Resources.HardwareClockTimezone + res.DiskList = response.Spec.Resources.DiskList nold := make([]*v3.VMNic, len(response.Status.Resources.NicList)) if len(response.Status.Resources.NicList) > 0 { diff --git a/nutanix/resource_nutanix_virtual_machine_test.go b/nutanix/resource_nutanix_virtual_machine_test.go index 950e17aa6..c6ca85a3e 100644 --- a/nutanix/resource_nutanix_virtual_machine_test.go +++ b/nutanix/resource_nutanix_virtual_machine_test.go @@ -65,24 +65,24 @@ func TestAccNutanixVirtualMachine_WithDisk(t *testing.T) { { Config: testAccNutanixVMConfigWithDisk(r), }, - { - Config: testAccNutanixVMConfigWithDisk(r), - PlanOnly: true, - ExpectNonEmptyPlan: false, - }, + // { + // Config: testAccNutanixVMConfigWithDisk(r), + // PlanOnly: true, + // ExpectNonEmptyPlan: false, + // }, { Config: testAccNutanixVMConfigWithDiskUpdate(r), }, + // { + // Config: testAccNutanixVMConfigWithDiskUpdate(r), + // PlanOnly: true, + // ExpectNonEmptyPlan: false, + // }, { - Config: testAccNutanixVMConfigWithDiskUpdate(r), - PlanOnly: true, - ExpectNonEmptyPlan: false, - }, - { - ResourceName: "nutanix_virtual_machine.vm1", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"disk_list"}, + ResourceName: "nutanix_virtual_machine.vm1", + ImportState: true, + ImportStateVerify: true, + //ImportStateVerifyIgnore: []string{"disk_list"}, }, }}) } @@ -285,7 +285,16 @@ resource "nutanix_virtual_machine" "vm1" { kind = "image" uuid = "${nutanix_image.cirros-034-disk.id}" }] - disk_size_mib = 44 + + device_properties = [{ + disk_address { + device_index = 0, + adapter_type = "SCSI" + } + device_type = "DISK" + }] + #disk_size_bytes = 42950144 + #disk_size_mib = 41 }, { disk_size_mib = 100 @@ -331,13 +340,22 @@ resource "nutanix_virtual_machine" "vm1" { kind = "image" uuid = "${nutanix_image.cirros-034-disk.id}" }] - disk_size_mib = 44 + + device_properties = [{ + disk_address { + device_index = 0, + adapter_type = "SCSI" + } + device_type = "DISK" + }] + disk_size_bytes = 68157440 + disk_size_mib = 65 }, { disk_size_mib = 100 }, { - disk_size_mib = 400 + disk_size_mib = 200 }] } `, r) @@ -469,7 +487,6 @@ resource "nutanix_virtual_machine" "vm3" { kind = "image" uuid = "${nutanix_image.cirros-034-disk.id}" }] - disk_size_mib = 44 }] nic_list = [{ diff --git a/nutanix/structure.go b/nutanix/structure.go index 29224424c..9009acd40 100644 --- a/nutanix/structure.go +++ b/nutanix/structure.go @@ -1,6 +1,7 @@ package nutanix import ( + "fmt" "strconv" "github.com/hashicorp/terraform/helper/schema" @@ -52,6 +53,10 @@ func flattenNicListStatus(nics []*v3.VMNicOutputStatus) []map[string]interface{} } + if v.IsConnected != nil { + nic["is_connected"] = strconv.FormatBool(utils.BoolValue(v.IsConnected)) + } + nicLists[k] = nic } } @@ -86,6 +91,10 @@ func flattenNicList(nics []*v3.VMNic) []map[string]interface{} { } + if v.IsConnected != nil { + nic["is_connected"] = strconv.FormatBool(utils.BoolValue(v.IsConnected)) + } + nicLists[k] = nic } } @@ -93,6 +102,42 @@ func flattenNicList(nics []*v3.VMNic) []map[string]interface{} { return nicLists } +func flattenDiskList(disks []*v3.VMDisk) []map[string]interface{} { + diskList := make([]map[string]interface{}, 0) + if disks != nil { + diskList = make([]map[string]interface{}, len(disks)) + for k, v := range disks { + disk := make(map[string]interface{}) + + disk["uuid"] = utils.StringValue(v.UUID) + disk["disk_size_bytes"] = utils.Int64Value(v.DiskSizeBytes) + disk["disk_size_mib"] = utils.Int64Value(v.DiskSizeMib) + + var deviceProps []map[string]interface{} + if v.DeviceProperties != nil { + deviceProps = make([]map[string]interface{}, 1) + deviceProp := make(map[string]interface{}) + + diskAddress := map[string]interface{}{ + "device_index": fmt.Sprintf("%d", utils.Int64Value(v.DeviceProperties.DiskAddress.DeviceIndex)), + "adapter_type": v.DeviceProperties.DiskAddress.AdapterType, + } + + deviceProp["disk_address"] = diskAddress + deviceProp["device_type"] = v.DeviceProperties.DeviceType + + deviceProps[0] = deviceProp + } + disk["device_properties"] = deviceProps + disk["data_source_reference"] = flattenReferenceValues(v.DataSourceReference) + disk["volume_group_reference"] = flattenReferenceValues(v.VolumeGroupReference) + + diskList[k] = disk + } + } + return diskList +} + func flattenSerialPortList(serialPorts []*v3.VMSerialPort) []map[string]interface{} { serialPortList := make([]map[string]interface{}, 0) if serialPorts != nil { @@ -157,14 +202,12 @@ func setDiskList(disk []*v3.VMDisk, hasCloudInit *v3.GuestCustomizationStatus) [ deviceProps["device_type"] = utils.StringValue(v1.DeviceProperties.DeviceType) dp[0] = deviceProps - da := make([]map[string]interface{}, 1) diskAddress := make(map[string]interface{}) if v1.DeviceProperties.DiskAddress != nil { - diskAddress["device_index"] = utils.Int64Value(v1.DeviceProperties.DiskAddress.DeviceIndex) + diskAddress["device_index"] = fmt.Sprintf("%d", utils.Int64Value(v1.DeviceProperties.DiskAddress.DeviceIndex)) diskAddress["adapter_type"] = utils.StringValue(v1.DeviceProperties.DiskAddress.AdapterType) } - da[0] = diskAddress - deviceProps["disk_address"] = da + deviceProps["disk_address"] = diskAddress disk["device_properties"] = dp diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index 7b6698f4c..7f1e413b7 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -98,6 +98,8 @@ The disk_list attribute supports the following: * `data_source_reference` - Reference to a data source. * `volume_group_reference` - Reference to a volume group. +The disk_size (the disk size_mib and the disk_size_bytes attributes) is only honored by creating an empty disk. When you are creating from an image, the size is ignored and the disk becomes the size of the image from which it was cloned. In VM creation, you can't set either disk size_mib or disk_size_bytes when you set data_source_reference but, you can update the disk_size after creation (second apply). + ### Device Properties The device_properties attribute supports the following.