From 62bfad2518e942de520c580665e7c33931e9adad Mon Sep 17 00:00:00 2001 From: Manish Tiwari Date: Mon, 21 Oct 2019 05:35:36 -0700 Subject: [PATCH 1/7] SPBM storage policy enablement for VM provisioning. Storage policy can be applied to VM and virtual disk in Create and Update operations. Website docs added for the code changes. Adding the docs related to storage policy for website. ng description in docs of data source storage policy. --- vsphere/data_source_vsphere_storage_policy.go | 31 ++++ vsphere/internal/helper/spbm/spbm_helper.go | 142 ++++++++++++++++++ .../virtual_machine_disk_subresource.go | 45 ++++++ vsphere/provider.go | 1 + vsphere/virtual_machine_config_structure.go | 17 +++ website/docs/d/storage_policy.html.markdown | 35 +++++ website/docs/r/virtual_machine.html.markdown | 3 + 7 files changed, 274 insertions(+) create mode 100644 vsphere/data_source_vsphere_storage_policy.go create mode 100644 vsphere/internal/helper/spbm/spbm_helper.go create mode 100644 website/docs/d/storage_policy.html.markdown diff --git a/vsphere/data_source_vsphere_storage_policy.go b/vsphere/data_source_vsphere_storage_policy.go new file mode 100644 index 000000000..62edb7bba --- /dev/null +++ b/vsphere/data_source_vsphere_storage_policy.go @@ -0,0 +1,31 @@ +package vsphere + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/spbm" +) + +func dataSourceVSphereStoragePolicy() *schema.Resource { + return &schema.Resource{ + Read: dataSourceVSphereStoragePolicyRead, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "The display name of the storage policy.", + Required: true, + }, + }, + } +} + +func dataSourceVSphereStoragePolicyRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*VSphereClient).vimClient + + id, err := spbm.PolicyIDByName(client, d.Get("name").(string)) + if err != nil { + return err + } + + d.SetId(id) + return nil +} diff --git a/vsphere/internal/helper/spbm/spbm_helper.go b/vsphere/internal/helper/spbm/spbm_helper.go new file mode 100644 index 000000000..5dcfdd463 --- /dev/null +++ b/vsphere/internal/helper/spbm/spbm_helper.go @@ -0,0 +1,142 @@ +package spbm + +import ( + "context" + "fmt" + "log" + + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/provider" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/viapi" + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/pbm" + "github.com/vmware/govmomi/pbm/methods" + pbmtypes "github.com/vmware/govmomi/pbm/types" + "github.com/vmware/govmomi/vim25/types" +) + +// pbmClientFromGovmomiClient creates a new pbm client from given govmomi client. +// Can we have it in govmomi client as a field similar to tag client? +// We should not create a new pbm client every time we need it. +func pbmClientFromGovmomiClient(ctx context.Context, client *govmomi.Client) (*pbm.Client, error) { + if err := viapi.ValidateVirtualCenter(client); err != nil { + return nil, err + } + + pc, err := pbm.NewClient(ctx, client.Client) + return pc, err + +} + +// PolicyIDByName finds a SPBM storage policy by name and returns its ID. +func PolicyIDByName(client *govmomi.Client, name string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout) + defer cancel() + pc, err := pbmClientFromGovmomiClient(ctx, client) + if err != nil { + return "", err + } + + return pc.ProfileIDByName(ctx, name) +} + +// policyNameByID returns storage policy name by its ID. +func policyNameByID(client *govmomi.Client, id string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout) + defer cancel() + pc, err := pbmClientFromGovmomiClient(ctx, client) + if err != nil { + return "", err + } + + log.Printf("[DEBUG] Retrieving contents of storage profiles by id: %s.", id) + profileId := []pbmtypes.PbmProfileId{ + pbmtypes.PbmProfileId{ + UniqueId: id, + }, + } + policies, err := pc.RetrieveContent(ctx, profileId) + if err != nil { + return "", err + } + + return policies[0].GetPbmProfile().Name, err +} + +// PolicySpecByID creates and returns VirtualMachineDefinedProfileSpec by policy ID. +func PolicySpecByID(id string) []types.BaseVirtualMachineProfileSpec { + return []types.BaseVirtualMachineProfileSpec{ + &types.VirtualMachineDefinedProfileSpec{ + ProfileId: id, + }, + } +} + +// PolicyIDByVirtualDisk fetches the storage policy associated with a virtual disk. +func PolicyIDByVirtualDisk(client *govmomi.Client, vmMOID string, diskKey int) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout) + defer cancel() + pc, err := pbmClientFromGovmomiClient(ctx, client) + if err != nil { + return "", err + } + + pbmSOR := pbmtypes.PbmServerObjectRef{ + ObjectType: "virtualDiskId", + Key: fmt.Sprintf("%s:%d", vmMOID, diskKey), + } + + policies, err := queryAssociatedProfile(ctx, pc, pbmSOR) + if err != nil { + return "", err + } + + // If no policy returned then virtual disk is not associated with a policy + if policies == nil || len(policies) == 0 { + return "", nil + } + + return policies[0].UniqueId, nil +} + +// PolicyIDByVirtualMachine fetches the storage policy associated with a virtual machine. +func PolicyIDByVirtualMachine(client *govmomi.Client, vmMOID string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout) + defer cancel() + pc, err := pbmClientFromGovmomiClient(ctx, client) + if err != nil { + return "", err + } + + pbmSOR := pbmtypes.PbmServerObjectRef{ + ObjectType: "virtualMachine", + Key: vmMOID, + } + + policies, err := queryAssociatedProfile(ctx, pc, pbmSOR) + if err != nil { + return "", err + } + + // If no policy returned then VM is not associated with a policy + if policies == nil || len(policies) == 0 { + return "", nil + } + + return policies[0].UniqueId, nil +} + +// queryAssociatedProfile returns the PbmProfileId of the storage policy associated with entity. +func queryAssociatedProfile(ctx context.Context, pc *pbm.Client, ref pbmtypes.PbmServerObjectRef) ([]pbmtypes.PbmProfileId, error) { + log.Printf("[DEBUG] queryAssociatedProfile: Retrieving storage policy of server object of type [%s] and key [%s].", ref.ObjectType, ref.Key) + req := pbmtypes.PbmQueryAssociatedProfile{ + This: pc.ServiceContent.ProfileManager, + Entity: ref, + } + + res, err := methods.PbmQueryAssociatedProfile(ctx, pc, &req) + if err != nil { + return nil, err + } + + return res.Returnval, nil +} diff --git a/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go b/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go index 7de2ce1ba..5116ebc8c 100644 --- a/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go +++ b/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go @@ -15,9 +15,11 @@ import ( "github.com/hashicorp/terraform/helper/validation" "github.com/mitchellh/copystructure" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/datastore" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/spbm" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/storagepod" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/structure" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/viapi" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/virtualmachine" "github.com/vmware/govmomi" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/vim25/types" @@ -215,6 +217,11 @@ func DiskSubresourceSchema() map[string]*schema.Schema { ConflictsWith: []string{"datastore_cluster_id"}, Description: "If this is true, the disk is attached instead of created. Implies keep_on_remove.", }, + "storage_policy_id": { + Type: schema.TypeString, + Optional: true, + Description: "The ID of the storage policy to assign to the virtual disk in VM.", + }, } structure.MergeSchema(s, subresourceSchema()) return s @@ -1189,6 +1196,12 @@ func (r *DiskSubresource) Create(l object.VirtualDeviceList) ([]types.BaseVirtua if r.Get("attach").(bool) { dspec[0].GetVirtualDeviceConfigSpec().FileOperation = "" } + + // Attach the SPBM storage policy if specified + if policyID := r.Get("storage_policy_id").(string); policyID != "" { + dspec[0].GetVirtualDeviceConfigSpec().Profile = spbm.PolicySpecByID(policyID) + } + spec = append(spec, dspec...) log.Printf("[DEBUG] %s: Device config operations from create: %s", r, DeviceChangeString(spec)) log.Printf("[DEBUG] %s: Create finished", r) @@ -1260,6 +1273,27 @@ func (r *DiskSubresource) Read(l object.VirtualDeviceList) error { r.Set("io_share_count", shares.Shares) } } + + // Set storage policy if either it is template VM with clone going on + // or VM already exists with update going on. + vmUUID := r.rdd.Get("clone.0.template_uuid").(string) + if vmUUID == "" { + // VM is not template. Check for UUID. + vmUUID = r.rdd.Get("uuid").(string) + } + if vmUUID != "" { + vm, err := virtualmachine.FromUUID(r.client, vmUUID) + if err != nil { + return err + } + vmMOID := vm.Reference().Value + polID, err := spbm.PolicyIDByVirtualDisk(r.client, vmMOID, r.Get("key").(int)) + if err != nil { + return err + } + r.Set("storage_policy_id", polID) + } + log.Printf("[DEBUG] %s: Read finished (key and device address may have changed)", r) return nil } @@ -1304,6 +1338,12 @@ func (r *DiskSubresource) Update(l object.VirtualDeviceList) ([]types.BaseVirtua } // Clear file operation - VirtualDeviceList currently sets this to replace, which is invalid dspec[0].GetVirtualDeviceConfigSpec().FileOperation = "" + + // Attach the SPBM storage policy if specified + if policyID := r.Get("storage_policy_id").(string); policyID != "" { + dspec[0].GetVirtualDeviceConfigSpec().Profile = spbm.PolicySpecByID(policyID) + } + log.Printf("[DEBUG] %s: Device config operations from update: %s", r, DeviceChangeString(dspec)) log.Printf("[DEBUG] %s: Update complete", r) return dspec, nil @@ -1611,6 +1651,11 @@ func (r *DiskSubresource) Relocate(l object.VirtualDeviceList, clone bool) (type relocate.DiskBackingInfo = backing } + // Attach the SPBM storage policy if specified + if policyID := r.Get("storage_policy_id").(string); policyID != "" { + relocate.Profile = spbm.PolicySpecByID(policyID) + } + // Done! log.Printf("[DEBUG] %s: Generated disk locator: %s", r, diskRelocateString(relocate)) log.Printf("[DEBUG] %s: Relocate generation complete", r) diff --git a/vsphere/provider.go b/vsphere/provider.go index d396c5f54..13266ce9e 100644 --- a/vsphere/provider.go +++ b/vsphere/provider.go @@ -141,6 +141,7 @@ func Provider() terraform.ResourceProvider { "vsphere_host": dataSourceVSphereHost(), "vsphere_network": dataSourceVSphereNetwork(), "vsphere_resource_pool": dataSourceVSphereResourcePool(), + "vsphere_storage_policy": dataSourceVSphereStoragePolicy(), "vsphere_tag": dataSourceVSphereTag(), "vsphere_tag_category": dataSourceVSphereTagCategory(), "vsphere_vapp_container": dataSourceVSphereVAppContainer(), diff --git a/vsphere/virtual_machine_config_structure.go b/vsphere/virtual_machine_config_structure.go index fb8b22add..23d45be03 100644 --- a/vsphere/virtual_machine_config_structure.go +++ b/vsphere/virtual_machine_config_structure.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/spbm" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/structure" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/viapi" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/virtualmachine" @@ -277,6 +278,11 @@ func schemaVirtualMachineConfigSpec() map[string]*schema.Schema { Computed: true, Description: "The UUID of the virtual machine. Also exposed as the ID of the resource.", }, + "storage_policy_id": { + Type: schema.TypeString, + Optional: true, + Description: "The ID of the storage policy to assign to the virtual machine home directory.", + }, } structure.MergeSchema(s, schemaVirtualMachineResourceAllocation()) return s @@ -752,6 +758,16 @@ func expandMemorySizeConfig(d *schema.ResourceData) int64 { return newMem } +// expandVirtualMachineProfileSpec reads storage policy ID from ResourceData and +// returns VirtualMachineProfileSpec. +func expandVirtualMachineProfileSpec(d *schema.ResourceData) []types.BaseVirtualMachineProfileSpec { + if policyID := d.Get("storage_policy_id").(string); policyID != "" { + return spbm.PolicySpecByID(policyID) + } + + return nil +} + // expandVirtualMachineConfigSpec reads certain ResourceData keys and // returns a VirtualMachineConfigSpec. func expandVirtualMachineConfigSpec(d *schema.ResourceData, client *govmomi.Client) (types.VirtualMachineConfigSpec, error) { @@ -785,6 +801,7 @@ func expandVirtualMachineConfigSpec(d *schema.ResourceData, client *govmomi.Clie NestedHVEnabled: getBoolWithRestart(d, "nested_hv_enabled"), VPMCEnabled: getBoolWithRestart(d, "cpu_performance_counters_enabled"), LatencySensitivity: expandLatencySensitivity(d), + VmProfile: expandVirtualMachineProfileSpec(d), } return obj, nil diff --git a/website/docs/d/storage_policy.html.markdown b/website/docs/d/storage_policy.html.markdown new file mode 100644 index 000000000..e4481e566 --- /dev/null +++ b/website/docs/d/storage_policy.html.markdown @@ -0,0 +1,35 @@ +--- +layout: "vsphere" +page_title: "VMware vSphere: vsphere_storage_policy" +sidebar_current: "docs-vsphere-data-source-storage-policy" +description: |- + A data source that can be used to get the UUID of a storage policy. +--- + +# vsphere\_storage\_policy + +The `vsphere_storage_policy` data source can be used to discover the UUID of a +vSphere storage policy. This can then be used with resources or data sources that +require a storage policy. + +~> **NOTE:** Storage policy support is unsupported on direct ESXi connections and +requires vCenter 6.0 or higher. + +## Example Usage + +```hcl +data "vsphere_storage_policy" "policy" { + name = "policy1" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the storage policy. + +## Attribute Reference + +The only exported attribute is `id`, which is the UUID of this storage policy. + diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index cf4220265..9840f4fcd 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -513,6 +513,8 @@ requires vCenter 6.0 or higher. ~> **NOTE:** Custom attributes are unsupported on direct ESXi connections and require vCenter. +* `storage_policy_id` - (Optional) The UUID of the storage policy to assign to VM home directory. + ### CPU and memory options The following options control CPU and memory settings on the virtual machine: @@ -797,6 +799,7 @@ externally with `attach` when the `path` field is not specified. be one of `low`, `normal`, `high`, or `custom`. Default: `normal`. * `io_share_count` - (Optional) The share count for this disk when the share level is `custom`. +* `storage_policy_id` - (Optional) The UUID of the storage policy to assign to this disk. #### Computed disk attributes From 3fadd5d2e46bb2afe86bf421c6964b94b7062d16 Mon Sep 17 00:00:00 2001 From: Manish Tiwari Date: Fri, 1 Nov 2019 03:37:18 -0700 Subject: [PATCH 2/7] Using convenience function to get the MOID from the UUID of a VM. --- .../virtualdevice/virtual_machine_disk_subresource.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go b/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go index 5116ebc8c..de02cd362 100644 --- a/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go +++ b/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go @@ -1277,17 +1277,12 @@ func (r *DiskSubresource) Read(l object.VirtualDeviceList) error { // Set storage policy if either it is template VM with clone going on // or VM already exists with update going on. vmUUID := r.rdd.Get("clone.0.template_uuid").(string) - if vmUUID == "" { - // VM is not template. Check for UUID. - vmUUID = r.rdd.Get("uuid").(string) - } if vmUUID != "" { - vm, err := virtualmachine.FromUUID(r.client, vmUUID) + result, err := virtualmachine.MOIDForUUID(r.client, vmUUID) if err != nil { return err } - vmMOID := vm.Reference().Value - polID, err := spbm.PolicyIDByVirtualDisk(r.client, vmMOID, r.Get("key").(int)) + polID, err := spbm.PolicyIDByVirtualDisk(r.client, result.MOID, r.Get("key").(int)) if err != nil { return err } From 8f10507a683a2a1381652e66cfe52dd946d1ce93 Mon Sep 17 00:00:00 2001 From: Manish Tiwari Date: Mon, 21 Oct 2019 05:35:36 -0700 Subject: [PATCH 3/7] SPBM storage policy enablement for VM provisioning. Storage policy can be applied to VM and virtual disk in Create and Update operations. Website docs added for the code changes. Adding the docs related to storage policy for website. ng description in docs of data source storage policy. --- .../virtualdevice/virtual_machine_disk_subresource.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go b/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go index de02cd362..5116ebc8c 100644 --- a/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go +++ b/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go @@ -1277,12 +1277,17 @@ func (r *DiskSubresource) Read(l object.VirtualDeviceList) error { // Set storage policy if either it is template VM with clone going on // or VM already exists with update going on. vmUUID := r.rdd.Get("clone.0.template_uuid").(string) + if vmUUID == "" { + // VM is not template. Check for UUID. + vmUUID = r.rdd.Get("uuid").(string) + } if vmUUID != "" { - result, err := virtualmachine.MOIDForUUID(r.client, vmUUID) + vm, err := virtualmachine.FromUUID(r.client, vmUUID) if err != nil { return err } - polID, err := spbm.PolicyIDByVirtualDisk(r.client, result.MOID, r.Get("key").(int)) + vmMOID := vm.Reference().Value + polID, err := spbm.PolicyIDByVirtualDisk(r.client, vmMOID, r.Get("key").(int)) if err != nil { return err } From 7099ff9f943336dccd17174a1e41a9066ceeea8d Mon Sep 17 00:00:00 2001 From: Manish Tiwari Date: Fri, 1 Nov 2019 03:37:18 -0700 Subject: [PATCH 4/7] Using convenience function to get the MOID from the UUID of a VM. --- .../virtualdevice/virtual_machine_disk_subresource.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go b/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go index 5116ebc8c..de02cd362 100644 --- a/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go +++ b/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go @@ -1277,17 +1277,12 @@ func (r *DiskSubresource) Read(l object.VirtualDeviceList) error { // Set storage policy if either it is template VM with clone going on // or VM already exists with update going on. vmUUID := r.rdd.Get("clone.0.template_uuid").(string) - if vmUUID == "" { - // VM is not template. Check for UUID. - vmUUID = r.rdd.Get("uuid").(string) - } if vmUUID != "" { - vm, err := virtualmachine.FromUUID(r.client, vmUUID) + result, err := virtualmachine.MOIDForUUID(r.client, vmUUID) if err != nil { return err } - vmMOID := vm.Reference().Value - polID, err := spbm.PolicyIDByVirtualDisk(r.client, vmMOID, r.Get("key").(int)) + polID, err := spbm.PolicyIDByVirtualDisk(r.client, result.MOID, r.Get("key").(int)) if err != nil { return err } From 266bcea90f5d5d77f3b1ec66c6bc7cdd54270ba7 Mon Sep 17 00:00:00 2001 From: Manish Tiwari Date: Thu, 16 Jan 2020 10:47:29 -0800 Subject: [PATCH 5/7] Adding Read for storage policy in virtual machine --- vsphere/resource_vsphere_virtual_machine.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vsphere/resource_vsphere_virtual_machine.go b/vsphere/resource_vsphere_virtual_machine.go index 59378b9df..878ff662b 100644 --- a/vsphere/resource_vsphere_virtual_machine.go +++ b/vsphere/resource_vsphere_virtual_machine.go @@ -16,6 +16,7 @@ import ( "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/folder" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/hostsystem" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/resourcepool" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/spbm" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/storagepod" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/structure" "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/vappcontainer" @@ -439,6 +440,13 @@ func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) if err := flattenVirtualMachineConfigInfo(d, vprops.Config); err != nil { return fmt.Errorf("error reading virtual machine configuration: %s", err) } + + // Read the VM Home storage policy if associated + polID, err := spbm.PolicyIDByVirtualMachine(client, moid) + if err != nil { + return err + } + d.Set("storage_policy_id", polID) // Perform pending device read operations. devices := object.VirtualDeviceList(vprops.Config.Hardware.Device) From 74db94dc7e9ef614378adb364aa84deb9a96922d Mon Sep 17 00:00:00 2001 From: Bill Rich Date: Thu, 19 Dec 2019 16:17:01 -0800 Subject: [PATCH 6/7] d/storage_policy: Add acceptance tests --- tf-vsphere-devrc.mk.example | 99 ++++++++++--------- ...data_source_vsphere_storage_policy_test.go | 49 +++++++++ .../virtual_machine_disk_subresource.go | 1 - .../resource_vsphere_virtual_machine_test.go | 98 ++++++++++++++++++ 4 files changed, 197 insertions(+), 50 deletions(-) create mode 100644 vsphere/data_source_vsphere_storage_policy_test.go diff --git a/tf-vsphere-devrc.mk.example b/tf-vsphere-devrc.mk.example index 1fada5989..b2ad9efa3 100644 --- a/tf-vsphere-devrc.mk.example +++ b/tf-vsphere-devrc.mk.example @@ -21,55 +21,56 @@ export VSPHERE_ALLOW_UNVERIFIED_SSL ?= false # The following variables are shared across various tests. To ensure all tests # succeed, it's probably best to set all of these to valid values. -export VSPHERE_TEMPLATE ?= base-linux # VM template to clone -export VSPHERE_TEMPLATE_WINDOWS ?= base-win # Windows VM template -export VSPHERE_TEMPLATE_COREOS ?= base-core # CoreOS template from OVA -export VSPHERE_TEMPLATE_ISO_TRANSPORT ?= base-vappt # Template with vApp ISO transport -export VSPHERE_NETWORK_LABEL ?= vm-network # Port group label -export VSPHERE_NETWORK_LABEL_DHCP ?= vm-network # Port group label for DHCP -export VSPHERE_NETWORK_LABEL_PXE ?= vm-network # Port group label for PXE -export VSPHERE_IPV4_ADDRESS ?= 10.0.0.100 # Customization IP address -export VSPHERE_IPV4_PREFIX ?= 24 # Customization netmask -export VSPHERE_IPV4_GATEWAY ?= 10.0.0.1 # Customization gateway -export VSPHERE_DNS ?= 10.0.0.10 # Customization DNS -export VSPHERE_DATACENTER ?= vm-dc # VM placement DC -export VSPHERE_CLUSTER ?= vm-clus1 # VM placement cluster -export VSPHERE_CLUSTER2 ?= vm-clus2 # Extra cluster for testing -export VSPHERE_EMPTY_CLUSTER ?= clus-empty # Empty cluster for testing -export VSPHERE_RESOURCE_POOL ?= vm-respool # VM resource resource pool -export VSPHERE_DATASTORE ?= datastore1 # VM placement datastore -export VSPHERE_DATASTORE2 ?= datastore2 # 2nd datastore for vMotion -export VSPHERE_INIT_TYPE ?= thin # vDisk type -export VSPHERE_ADAPTER_TYPE ?= lsiLogic # Virtual disk adapter type -export VSPHERE_LICENSE ?= key # License resource test key -export VSPHERE_DC_FOLDER ?= dc-folder # DC resource test folder -export VSPHERE_ESXI_HOST ?= esxi1 # ESXi host to work with -export VSPHERE_ESXI_HOST2 ?= esxi2 # 2nd ESXi host to work with -export VSPHERE_ESXI_HOST3 ?= esxi3 # 3nd ESXi host to work with -export VSPHERE_ESXI_HOST4 ?= esxi4 # 4th ESXi host to work with -export VSPHERE_ESXI_HOST5 ?= esxi5 # 5th ESXi host to work with -export VSPHERE_ESXI_HOST6 ?= esxi6 # 6th ESXi host to work with -export VSPHERE_ESXI_HOST7 ?= esxi7 # 7th ESXi host to work with -export VSPHERE_HOST_NIC0 ?= vmnic0 # NIC0 for host net tests -export VSPHERE_HOST_NIC1 ?= vmnic1 # NIC1 for host net tests -export VSPHERE_VMFS_EXPECTED ?= scsi-name # Name of expected SCSI disk -export VSPHERE_VMFS_REGEXP ?= expr # Regexp for SCSI disk search -export VSPHERE_DS_VMFS_DISK0 ?= scsi-name0 # 1st disk for vmfs_datastore -export VSPHERE_DS_VMFS_DISK1 ?= scsi-name1 # 2nd disk for vmfs_datastore -export VSPHERE_DS_VMFS_DISK2 ?= scsi-name2 # 3rd disk for vmfs_datastore -export VSPHERE_DS_FOLDER ?= ds-folder # Path to a datastore folder -export VSPHERE_NAS_HOST ?= nas-host # Hostname for nas_datastore -export VSPHERE_NFS_PATH ?= nfs-path # NFS path for nas_datastore -export VSPHERE_NFS_PATH2 ?= nfs-path # 2nd nas_datastore path -export VSPHERE_FOLDER_V0_PATH ?= old-folder # vsphere_folder state test -export VSPHERE_ISO_FILE ?= iso-file # ISO file for CDROM device -export VSPHERE_ISO_DATASTORE ?= iso-ds # ISO file for CDROM device -export VSPHERE_VM_V1_PATH ?= vm-path # VM resource state migration -export VSPHERE_VAPP_CONTAINER ?= vapp-path # Path to a vApp container -export VSPHERE_PERSIST_SESSION ?= true # Session persistence -export VSPHERE_REST_SESSION_PATH ?= rest-path # Path to store rest sessions -export VSPHERE_VIM_SESSION_PATH ?= vim-path # Path to store vim sessions +export VSPHERE_TEMPLATE ?= base-linux # VM template to clone +export VSPHERE_TEMPLATE_WINDOWS ?= base-win # Windows VM template +export VSPHERE_TEMPLATE_COREOS ?= base-core # CoreOS template from OVA +export VSPHERE_TEMPLATE_ISO_TRANSPORT ?= base-vappt # Template with vApp ISO transport +export VSPHERE_NETWORK_LABEL ?= vm-network # Port group label +export VSPHERE_NETWORK_LABEL_DHCP ?= vm-network # Port group label for DHCP +export VSPHERE_NETWORK_LABEL_PXE ?= vm-network # Port group label for PXE +export VSPHERE_IPV4_ADDRESS ?= 10.0.0.100 # Customization IP address +export VSPHERE_IPV4_PREFIX ?= 24 # Customization netmask +export VSPHERE_IPV4_GATEWAY ?= 10.0.0.1 # Customization gateway +export VSPHERE_DNS ?= 10.0.0.10 # Customization DNS +export VSPHERE_DATACENTER ?= vm-dc # VM placement DC +export VSPHERE_CLUSTER ?= vm-clus1 # VM placement cluster +export VSPHERE_CLUSTER2 ?= vm-clus2 # Extra cluster for testing +export VSPHERE_EMPTY_CLUSTER ?= clus-empty # Empty cluster for testing +export VSPHERE_RESOURCE_POOL ?= vm-respool # VM resource resource pool +export VSPHERE_DATASTORE ?= datastore1 # VM placement datastore +export VSPHERE_DATASTORE2 ?= datastore2 # 2nd datastore for vMotion +export VSPHERE_INIT_TYPE ?= thin # vDisk type +export VSPHERE_ADAPTER_TYPE ?= lsiLogic # Virtual disk adapter type +export VSPHERE_LICENSE ?= key # License resource test key +export VSPHERE_DC_FOLDER ?= dc-folder # DC resource test folder +export VSPHERE_ESXI_HOST ?= esxi1 # ESXi host to work with +export VSPHERE_ESXI_HOST2 ?= esxi2 # 2nd ESXi host to work with +export VSPHERE_ESXI_HOST3 ?= esxi3 # 3nd ESXi host to work with +export VSPHERE_ESXI_HOST4 ?= esxi4 # 4th ESXi host to work with +export VSPHERE_ESXI_HOST5 ?= esxi5 # 5th ESXi host to work with +export VSPHERE_ESXI_HOST6 ?= esxi6 # 6th ESXi host to work with +export VSPHERE_ESXI_HOST7 ?= esxi7 # 7th ESXi host to work with +export VSPHERE_HOST_NIC0 ?= vmnic0 # NIC0 for host net tests +export VSPHERE_HOST_NIC1 ?= vmnic1 # NIC1 for host net tests +export VSPHERE_VMFS_EXPECTED ?= scsi-name # Name of expected SCSI disk +export VSPHERE_VMFS_REGEXP ?= expr # Regexp for SCSI disk search +export VSPHERE_DS_VMFS_DISK0 ?= scsi-name0 # 1st disk for vmfs_datastore +export VSPHERE_DS_VMFS_DISK1 ?= scsi-name1 # 2nd disk for vmfs_datastore +export VSPHERE_DS_VMFS_DISK2 ?= scsi-name2 # 3rd disk for vmfs_datastore +export VSPHERE_DS_FOLDER ?= ds-folder # Path to a datastore folder +export VSPHERE_NAS_HOST ?= nas-host # Hostname for nas_datastore +export VSPHERE_NFS_PATH ?= nfs-path # NFS path for nas_datastore +export VSPHERE_NFS_PATH2 ?= nfs-path # 2nd nas_datastore path +export VSPHERE_FOLDER_V0_PATH ?= old-folder # vsphere_folder state test +export VSPHERE_ISO_FILE ?= iso-file # ISO file for CDROM device +export VSPHERE_ISO_DATASTORE ?= iso-ds # ISO file for CDROM device +export VSPHERE_VM_V1_PATH ?= vm-path # VM resource state migration +export VSPHERE_VAPP_CONTAINER ?= vapp-path # Path to a vApp container +export VSPHERE_PERSIST_SESSION ?= true # Session persistence +export VSPHERE_REST_SESSION_PATH ?= rest-path # Path to store rest sessions +export VSPHERE_VIM_SESSION_PATH ?= vim-path # Path to store vim sessions export VSPHERE_VAPP_RESOURCE_POOL ?= vapp-rp-path #vSphere path to vApp's resource pool -export VSPHERE_CLONED_VM_DISK_SIZE ?= =32 # Size of disk attached to a cloned VM +export VSPHERE_CLONED_VM_DISK_SIZE ?= 32 # Size of disk attached to a cloned VM +export VSPHERE_STORAGE_POLICY ?= sp-test # Name of a vm storage policy # vi: filetype=make diff --git a/vsphere/data_source_vsphere_storage_policy_test.go b/vsphere/data_source_vsphere_storage_policy_test.go new file mode 100644 index 000000000..4e90c87d7 --- /dev/null +++ b/vsphere/data_source_vsphere_storage_policy_test.go @@ -0,0 +1,49 @@ +package vsphere + +import ( + "fmt" + "os" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataSourceVSphereStoragePolicy_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccDataSourceVSphereStoragePolicyPreCheck(t) + testAccSkipIfEsxi(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceVSphereStoragePolicyConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr("data.vsphere_storage_policy.storage_policy", "id", regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")), + ), + }, + }, + }) +} + +func testAccDataSourceVSphereStoragePolicyPreCheck(t *testing.T) { + if os.Getenv("VSPHERE_STORAGE_POLICY") == "" { + t.Skip("set VSPHERE_STORAGE_POLICY to run vsphere_storage_policy acceptance tests") + } +} + +func testAccDataSourceVSphereStoragePolicyConfig() string { + return fmt.Sprintf(` +variable "storage_policy" { + default = "%s" +} + +data "vsphere_storage_policy" "storage_policy" { + name = "${var.storage_policy}" +} +`, + os.Getenv("VSPHERE_STORAGE_POLICY"), + ) +} \ No newline at end of file diff --git a/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go b/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go index de02cd362..a603364a8 100644 --- a/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go +++ b/vsphere/internal/virtualdevice/virtual_machine_disk_subresource.go @@ -3,7 +3,6 @@ package virtualdevice import ( "errors" "fmt" - "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/virtualmachine" "log" "math" "path" diff --git a/vsphere/resource_vsphere_virtual_machine_test.go b/vsphere/resource_vsphere_virtual_machine_test.go index 66e701c18..b1ca9e8c2 100644 --- a/vsphere/resource_vsphere_virtual_machine_test.go +++ b/vsphere/resource_vsphere_virtual_machine_test.go @@ -77,6 +77,26 @@ func TestAccResourceVSphereVirtualMachine_basic(t *testing.T) { }) } +func TestAccResourceVSphereVirtualMachine_spbm(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereVirtualMachinePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereVirtualMachineCheckExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereVirtualMachineConfigSPBM(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereVirtualMachineCheckExists(true), + resource.TestMatchResourceAttr("vsphere_virtual_machine.vm", "storage_policy_id", regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")), + ), + }, + }, + }) +} + func TestAccResourceVSphereVirtualMachine_ignoreValidationOnComputedValue(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -3087,6 +3107,9 @@ func testAccResourceVSphereVirtualMachinePreCheck(t *testing.T) { if os.Getenv("VSPHERE_NFS_PATH2") == "" { t.Skip("set VSPHERE_NFS_PATH2 to run vsphere_virtual_machine acceptance tests") } + if os.Getenv("VSPHERE_STORAGE_POLICY") == "" { + t.Skip("set VSPHERE_STORAGE_POLICY to run vsphere_virtual_machine acceptance tests") + } } func testAccResourceVSphereVirtualMachineCheckExists(expected bool) resource.TestCheckFunc { @@ -4201,6 +4224,81 @@ resource "vsphere_virtual_machine" "vm" { ) } +func testAccResourceVSphereVirtualMachineConfigSPBM() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "resource_pool" { + default = "%s" +} + +variable "network_label" { + default = "%s" +} + +variable "datastore" { + default = "%s" +} + +variable "storage_policy" { + default = "%s" +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +data "vsphere_datastore" "datastore" { + name = "${var.datastore}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +data "vsphere_resource_pool" "pool" { + name = "${var.resource_pool}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +data "vsphere_network" "network" { + name = "${var.network_label}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +data "vsphere_storage_policy" "sp" { + name = "${var.storage_policy}" +} + +resource "vsphere_virtual_machine" "vm" { + name = "terraform-test" + resource_pool_id = "${data.vsphere_resource_pool.pool.id}" + datastore_id = "${data.vsphere_datastore.datastore.id}" + storage_policy_id = "${data.vsphere_storage_policy.sp.id}" + + num_cpus = 2 + memory = 2048 + guest_id = "other3xLinux64Guest" + + wait_for_guest_net_timeout = -1 + + network_interface { + network_id = "${data.vsphere_network.network.id}" + } + + disk { + label = "disk0" + size = 20 + } +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_RESOURCE_POOL"), + os.Getenv("VSPHERE_NETWORK_LABEL_PXE"), + os.Getenv("VSPHERE_DATASTORE"), + os.Getenv("VSPHERE_STORAGE_POLICY"), + ) +} + func testAccResourceVSphereVirtualMachineConfigSharedSCSIBus() string { return fmt.Sprintf(` variable "datacenter" { From 3222100ce7d664318e7c352a16249721f498c7ee Mon Sep 17 00:00:00 2001 From: Bill Rich Date: Fri, 24 Jan 2020 15:25:34 -0800 Subject: [PATCH 7/7] Fixup docs and formatting --- vsphere/resource_vsphere_virtual_machine.go | 2 +- website/docs/index.html.markdown | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/vsphere/resource_vsphere_virtual_machine.go b/vsphere/resource_vsphere_virtual_machine.go index 878ff662b..57247f298 100644 --- a/vsphere/resource_vsphere_virtual_machine.go +++ b/vsphere/resource_vsphere_virtual_machine.go @@ -440,7 +440,7 @@ func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) if err := flattenVirtualMachineConfigInfo(d, vprops.Config); err != nil { return fmt.Errorf("error reading virtual machine configuration: %s", err) } - + // Read the VM Home storage policy if associated polID, err := spbm.PolicyIDByVirtualMachine(client, moid) if err != nil { diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index db37df914..13306ee7a 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -133,7 +133,9 @@ will no longer need them. * `vim_session_path` - (Optional) The direcotry to save the VIM SOAP API session to. Default: `${HOME}/.govmomi/sessions`. Can also be specified by the `VSPHERE_VIM_SESSION_PATH` environment variable. -* `rest_session_path` - Deprecated +* `rest_session_path` - The directory to save the REST API session to. + Default: `${HOME}/.govmomi/rest_sessions`. Can also be specified by the + `VSPHERE_REST_SESSION_PATH` environment variable. #### govc/Terraform session interoperability