diff --git a/.changes/v3.8.0/904-deprecations.md b/.changes/v3.8.0/904-deprecations.md new file mode 100644 index 000000000..bc88a3178 --- /dev/null +++ b/.changes/v3.8.0/904-deprecations.md @@ -0,0 +1,3 @@ +* Deprecated `default_vm_sizing_policy_id` field in `vcd_org_vdc` resource and data source. This field is misleading as it + can contain not only VM Sizing Policies but also VM Placement Policies or vGPU Policies. + Its replacement is the `default_compute_policy_id` attribute [GH-904] diff --git a/.changes/v3.8.0/904-features.md b/.changes/v3.8.0/904-features.md new file mode 100644 index 000000000..34dfd371a --- /dev/null +++ b/.changes/v3.8.0/904-features.md @@ -0,0 +1,4 @@ +* **New Resource:** `vcd_vm_placement_policy` that allows creating VM Placement Policies [GH-904] +* **New Data Source:** `vcd_vm_placement_policy` that allows fetching existing VM Placement Policies [GH-904] +* **New Data Source:** `vcd_provider_vdc` that allows fetching existing Provider VDCs [GH-904] +* **New Data Source:** `vcd_vm_group` that allows fetching existing VM Groups, to be able to create VM Placement Policies [GH-904] diff --git a/.changes/v3.8.0/904-improvements.md b/.changes/v3.8.0/904-improvements.md new file mode 100644 index 000000000..e45fc900b --- /dev/null +++ b/.changes/v3.8.0/904-improvements.md @@ -0,0 +1,4 @@ +* Added `vm_placement_policy_ids` attribute to `vcd_org_vdc` resource and data source to assign existing + VM Placement Policies to VDCs [GH-904] +* Added `default_compute_policy_id` attribute to `vcd_org_vdc` resource and data source to specify a default + VM Sizing Policy, VM Placement Policy or vGPU Policy for the VDC [GH-904] \ No newline at end of file diff --git a/go.mod b/go.mod index 67b2df250..300a85a94 100644 --- a/go.mod +++ b/go.mod @@ -58,3 +58,5 @@ require ( google.golang.org/grpc v1.46.0 // indirect google.golang.org/protobuf v1.28.0 // indirect ) + +replace github.com/vmware/go-vcloud-director/v2 => github.com/adambarreiro/go-vcloud-director/v2 v2.17.0-alpha.1.0.20220906102404-20ad03790c56 diff --git a/go.sum b/go.sum index 25554a861..ba5b6385e 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C6 github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/adambarreiro/go-vcloud-director/v2 v2.17.0-alpha.1.0.20220906102404-20ad03790c56 h1:nDzQm8TXnuZ+GMUiSjiJ/tjOtivrxdr2CwhuqVhFIlc= +github.com/adambarreiro/go-vcloud-director/v2 v2.17.0-alpha.1.0.20220906102404-20ad03790c56/go.mod h1:VRA1ZLDf6CtL1atU1ceMj6/3h9HJg+zjBLaMNODF1qQ= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= @@ -203,8 +205,6 @@ github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvC github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/vmware/go-vcloud-director/v2 v2.17.0-alpha.2 h1:dKUeUdDimwPU78NIxeFAN+YRfBZ6q9tXeSPFKmyEGvk= -github.com/vmware/go-vcloud-director/v2 v2.17.0-alpha.2/go.mod h1:VRA1ZLDf6CtL1atU1ceMj6/3h9HJg+zjBLaMNODF1qQ= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= diff --git a/vcd/config_test.go b/vcd/config_test.go index 42170ed88..5e0ac4b8c 100644 --- a/vcd/config_test.go +++ b/vcd/config_test.go @@ -98,10 +98,11 @@ type TestConfig struct { StorageProfile2 string `json:"storageProfile2"` } `json:"providerVdc"` NsxtProviderVdc struct { - Name string `json:"name"` - StorageProfile string `json:"storageProfile"` - StorageProfile2 string `json:"storageProfile2"` - NetworkPool string `json:"networkPool"` + Name string `json:"name"` + StorageProfile string `json:"storageProfile"` + StorageProfile2 string `json:"storageProfile2"` + NetworkPool string `json:"networkPool"` + PlacementPolicyVmGroup string `json:"placementPolicyVmGroup"` // Name of the VM group to create VM Placement Policies } `json:"nsxtProviderVdc"` Catalog struct { Name string `json:"name,omitempty"` diff --git a/vcd/datasource_not_found_test.go b/vcd/datasource_not_found_test.go index cc3066486..e9c720169 100644 --- a/vcd/datasource_not_found_test.go +++ b/vcd/datasource_not_found_test.go @@ -207,6 +207,8 @@ func addMandatoryParams(dataSourceName string, mandatoryFields []string, t *test // Invalid fields which are required for some resources for search (usually they are used instead of `name`) case "vdc_group_id": templateFields = templateFields + `vdc_group_id = "urn:vcloud:vdcGroup:c19ec5b1-3403-4d00-b414-9da50066dc1e"` + "\n" + case "provider_vdc_id": + templateFields = templateFields + `provider_vdc_id = "urn:vcloud:providervdc:8453a2e2-1432-4e67-a312-8e713495eabc"` + "\n" case "rule_id": templateFields = templateFields + `rule_id = "347928347234"` + "\n" case "name": diff --git a/vcd/datasource_vcd_org_vdc.go b/vcd/datasource_vcd_org_vdc.go index 015256f0d..5f9e8d76c 100644 --- a/vcd/datasource_vcd_org_vdc.go +++ b/vcd/datasource_vcd_org_vdc.go @@ -192,7 +192,15 @@ func datasourceVcdOrgVdc() *schema.Resource { "vm_sizing_policy_ids": { Type: schema.TypeSet, Computed: true, - Description: "Set of VM sizing policy IDs", + Description: "Set of VM Sizing policy IDs", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "vm_placement_policy_ids": { + Type: schema.TypeSet, + Computed: true, + Description: "Set of VM Placement policy IDs", Elem: &schema.Schema{ Type: schema.TypeString, }, @@ -200,7 +208,13 @@ func datasourceVcdOrgVdc() *schema.Resource { "default_vm_sizing_policy_id": { Type: schema.TypeString, Computed: true, - Description: "ID of default VM sizing policy ID", + Deprecated: "Use `default_compute_policy_id` attribute instead, which can support VM Sizing Policies, VM Placement Policies and vGPU Policies", + Description: "ID of default VM Compute policy, which can be a VM Sizing Policy, VM Placement Policy or vGPU Policy", + }, + "default_compute_policy_id": { + Type: schema.TypeString, + Computed: true, + Description: "ID of default Compute policy for this VDC, which can be a VM Sizing Policy, VM Placement Policy or vGPU Policy", }, }, } diff --git a/vcd/datasource_vcd_org_vdc_test.go b/vcd/datasource_vcd_org_vdc_test.go index 6027a2beb..dee7f9e26 100644 --- a/vcd/datasource_vcd_org_vdc_test.go +++ b/vcd/datasource_vcd_org_vdc_test.go @@ -118,7 +118,11 @@ func validateResourceAndDataSource(t *testing.T, configText string, datasourceVd resource.TestMatchResourceAttr( "data."+datasourceVdc, "compute_capacity.0.memory.0.used", regexp.MustCompile(`^\d+$`)), resource.TestMatchResourceAttr( - "data."+datasourceVdc, "storage_profile.0.storage_used_in_mb", regexp.MustCompile(`^\d+$`))), + "data."+datasourceVdc, "storage_profile.0.storage_used_in_mb", regexp.MustCompile(`^\d+$`)), + resource.TestMatchResourceAttr( + "data."+datasourceVdc, "default_compute_policy_id", regexp.MustCompile(`urn:vcloud:vdcComputePolicy:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)), + resource.TestMatchResourceAttr( + "data."+datasourceVdc, "vm_sizing_policy_ids.#", regexp.MustCompile(`[1-9]`))), // At least 1 sizing policy (the System default) }, }, }) @@ -149,7 +153,9 @@ func validateDataSource(t *testing.T, configText string, datasourceVdc string) { resource.TestMatchResourceAttr("data."+datasourceVdc, "compute_capacity.0.memory.0.limit", regexp.MustCompile(`^\d+$`)), resource.TestMatchResourceAttr("data."+datasourceVdc, "compute_capacity.0.memory.0.allocated", regexp.MustCompile(`^\d+$`)), resource.TestMatchResourceAttr("data."+datasourceVdc, "compute_capacity.0.memory.0.reserved", regexp.MustCompile(`^\d+$`)), - resource.TestMatchResourceAttr("data."+datasourceVdc, "storage_profile.0.storage_used_in_mb", regexp.MustCompile(`^\d+$`))), + resource.TestMatchResourceAttr("data."+datasourceVdc, "storage_profile.0.storage_used_in_mb", regexp.MustCompile(`^\d+$`)), + resource.TestMatchResourceAttr("data."+datasourceVdc, "default_compute_policy_id", regexp.MustCompile(`urn:vcloud:vdcComputePolicy:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)), + resource.TestMatchResourceAttr("data."+datasourceVdc, "vm_sizing_policy_ids.#", regexp.MustCompile(`[1-9]`))), // At least 1 sizing policy (the System default) }, }, }) diff --git a/vcd/datasource_vcd_provider_vdc.go b/vcd/datasource_vcd_provider_vdc.go new file mode 100644 index 000000000..f90130229 --- /dev/null +++ b/vcd/datasource_vcd_provider_vdc.go @@ -0,0 +1,324 @@ +package vcd + +import ( + "context" + "fmt" + "github.com/vmware/go-vcloud-director/v2/types/v56" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// datasourceVcdProviderVdc defines the data source for a Provider VDC. +func datasourceVcdProviderVdc() *schema.Resource { + // This internal schema defines the Root Capacity of the Provider VDC. + rootCapacityUsage := func(typeOfCapacity string) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Description: fmt.Sprintf("Single-element list with an indicator of %s capacity available in the Provider VDC", typeOfCapacity), + // MaxItems: 1 - A computed field can't use "MaxItems", this is a reminder that this is a single-element list. + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allocation": { + Type: schema.TypeInt, + Computed: true, + Description: fmt.Sprintf("Allocated %s for this Provider VDC", typeOfCapacity), + }, + "overhead": { + Type: schema.TypeInt, + Computed: true, + Description: fmt.Sprintf("%s overhead for this Provider VDC", typeOfCapacity), + }, + "reserved": { + Type: schema.TypeInt, + Computed: true, + Description: fmt.Sprintf("Reserved %s for this Provider VDC", typeOfCapacity), + }, + "total": { + Type: schema.TypeInt, + Computed: true, + Description: fmt.Sprintf("Total %s for this Provider VDC", typeOfCapacity), + }, + "units": { + Type: schema.TypeString, + Computed: true, + Description: fmt.Sprintf("Units for the %s of this Provider VDC", typeOfCapacity), + }, + "used": { + Type: schema.TypeInt, + Computed: true, + Description: fmt.Sprintf("Used %s in this Provider VDC", typeOfCapacity), + }, + }, + }, + } + } + + return &schema.Resource{ + ReadContext: datasourceVcdProviderVdcRead, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the Provider VDC", + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: "Optional description of the Provider VDC", + }, + "status": { + Type: schema.TypeInt, + Computed: true, + Description: "Status of the Provider VDC, it can be -1 (creation failed), 0 (not ready), 1 (ready), 2 (unknown) or 3 (unrecognized)", + }, + "is_enabled": { + Type: schema.TypeBool, + Computed: true, + Description: "True if this Provider VDC is enabled and can provide resources to organization VDCs. A Provider VDC is always enabled on creation", + }, + "capabilities": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + Description: "Set of virtual hardware versions supported by this Provider VDC", + }, + "compute_capacity": { + Type: schema.TypeList, + Computed: true, + // MaxItems: 1 - A computed field can't use "MaxItems", this is a reminder that this is a single-element list. + Description: "Single-element list with an indicator of CPU and memory capacity", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cpu": rootCapacityUsage("CPU"), + "memory": rootCapacityUsage("Memory"), + "is_elastic": { + Type: schema.TypeBool, + Computed: true, + Description: "True if compute capacity can grow or shrink based on demand", + }, + "is_ha": { + Type: schema.TypeBool, + Computed: true, + Description: "True if compute capacity is highly available", + }, + }, + }, + }, + "compute_provider_scope": { + Type: schema.TypeString, + Computed: true, + Description: "Represents the compute fault domain for this Provider VDC. This value is a tenant-facing tag that is shown to tenants when viewing fault domains of the child Organization VDCs (for example, a VDC Group)", + }, + "highest_supported_hardware_version": { + Type: schema.TypeString, + Computed: true, + Description: "The highest virtual hardware version supported by this Provider VDC", + }, + "nsxt_manager_id": { + Type: schema.TypeString, + Computed: true, + Description: "ID of the registered NSX-T Manager that backs networking operations for this Provider VDC", + }, + "storage_container_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + Description: "Set of IDs of the vSphere datastores backing this provider VDC", + }, + "external_network_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + Description: "Set of IDs of external networks", + }, + "storage_profile_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + Description: "Set of IDs to the storage profiles available to this Provider VDC", + }, + "resource_pool_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + Description: "Set of IDs of the resource pools backing this provider VDC", + }, + "network_pool_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + Description: "Set IDs of the network pools used by this Provider VDC", + }, + "universal_network_pool_id": { + Type: schema.TypeString, + Computed: true, + Description: "ID of the universal network reference", + }, + "host_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + Description: "Set with all the hosts which are connected to VC server", + }, + "vcenter_id": { + Type: schema.TypeString, + Computed: true, + Description: "ID of the vCenter server that provides the resource pools and datastores", + }, + "metadata": { + Type: schema.TypeMap, + Computed: true, + Description: "Key and value pairs for Provider VDC metadata", + }, + }, + } +} + +func datasourceVcdProviderVdcRead(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + vcdClient := meta.(*VCDClient) + + providerVdcName := d.Get("name").(string) + extendedProviderVdc, err := vcdClient.GetProviderVdcExtendedByName(providerVdcName) + if err != nil { + log.Printf("[DEBUG] Could not find any extended Provider VDC with name %s: %s", providerVdcName, err) + return diag.Errorf("could not find any extended Provider VDC with name %s: %s", providerVdcName, err) + } + providerVdc, err := extendedProviderVdc.ToProviderVdc() + if err != nil { + log.Printf("[DEBUG] Could not find any Provider VDC with name %s: %s", providerVdcName, err) + return diag.Errorf("could not find any Provider VDC with name %s: %s", providerVdcName, err) + } + + dSet(d, "name", extendedProviderVdc.VMWProviderVdc.Name) + dSet(d, "description", extendedProviderVdc.VMWProviderVdc.Description) + dSet(d, "status", extendedProviderVdc.VMWProviderVdc.Status) + dSet(d, "is_enabled", extendedProviderVdc.VMWProviderVdc.IsEnabled) + dSet(d, "compute_provider_scope", extendedProviderVdc.VMWProviderVdc.ComputeProviderScope) + dSet(d, "highest_supported_hardware_version", extendedProviderVdc.VMWProviderVdc.HighestSupportedHardwareVersion) + + if extendedProviderVdc.VMWProviderVdc.NsxTManagerReference != nil { + dSet(d, "nsxt_manager_id", extendedProviderVdc.VMWProviderVdc.NsxTManagerReference.ID) + } + + if extendedProviderVdc.VMWProviderVdc.AvailableNetworks != nil { + if err = d.Set("external_network_ids", extractIdsFromReferences(extendedProviderVdc.VMWProviderVdc.AvailableNetworks.Network)); err != nil { + return diag.Errorf("error setting external_network_ids: %s", err) + } + } + + if extendedProviderVdc.VMWProviderVdc.DataStoreRefs != nil { + if err = d.Set("storage_container_ids", extractIdsFromVimObjectRefs(extendedProviderVdc.VMWProviderVdc.DataStoreRefs.VimObjectRef)); err != nil { + return diag.Errorf("error setting storage_container_ids: %s", err) + } + } + + if extendedProviderVdc.VMWProviderVdc.StorageProfiles != nil { + if err = d.Set("storage_profile_ids", extractIdsFromReferences(extendedProviderVdc.VMWProviderVdc.StorageProfiles.ProviderVdcStorageProfile)); err != nil { + return diag.Errorf("error setting storage_profile_ids: %s", err) + } + } + + if extendedProviderVdc.VMWProviderVdc.ResourcePoolRefs != nil { + if err = d.Set("resource_pool_ids", extractIdsFromVimObjectRefs(extendedProviderVdc.VMWProviderVdc.ResourcePoolRefs.VimObjectRef)); err != nil { + return diag.Errorf("error setting resource_pool_ids: %s", err) + } + } + + if extendedProviderVdc.VMWProviderVdc.NetworkPoolReferences != nil { + if err = d.Set("network_pool_ids", extractIdsFromReferences(extendedProviderVdc.VMWProviderVdc.NetworkPoolReferences.NetworkPoolReference)); err != nil { + return diag.Errorf("error setting network_pool_ids: %s", err) + } + } + + var items []string + if extendedProviderVdc.VMWProviderVdc.Capabilities != nil && extendedProviderVdc.VMWProviderVdc.Capabilities.SupportedHardwareVersions != nil { + items = append(items, extendedProviderVdc.VMWProviderVdc.Capabilities.SupportedHardwareVersions.SupportedHardwareVersion...) + } + if err = d.Set("capabilities", items); err != nil { + return diag.Errorf("error setting capabilities: %s", err) + } + + if extendedProviderVdc.VMWProviderVdc.HostReferences != nil { + if err = d.Set("host_ids", extractIdsFromReferences(extendedProviderVdc.VMWProviderVdc.HostReferences.HostReference)); err != nil { + return diag.Errorf("error setting host_ids: %s", err) + } + } + + if extendedProviderVdc.VMWProviderVdc.ComputeCapacity != nil { + if err = d.Set("compute_capacity", getComputeCapacityForProviderVdc(extendedProviderVdc.VMWProviderVdc.ComputeCapacity)); err != nil { + return diag.Errorf("error setting compute_capacity: %s", err) + } + } + + if extendedProviderVdc.VMWProviderVdc.AvailableUniversalNetworkPool != nil { + dSet(d, "universal_network_pool_id", extendedProviderVdc.VMWProviderVdc.AvailableUniversalNetworkPool.ID) + } + if extendedProviderVdc.VMWProviderVdc.VimServer != nil { + dSet(d, "vcenter_id", extendedProviderVdc.VMWProviderVdc.VimServer.ID) + } + + metadata, err := providerVdc.GetMetadata() + if err != nil { + log.Printf("[DEBUG] Error retrieving metadata for Provider VDC: %s", err) + return diag.Errorf("error retrieving metadata for Provider VDC %s: %s", providerVdcName, err) + } + if err = d.Set("metadata", getMetadataStruct(metadata.MetadataEntry)); err != nil { + return diag.Errorf("There was an issue when setting metadata into the schema - %s", err) + } + + d.SetId(providerVdc.ProviderVdc.ID) + return nil +} + +// getComputeCapacityForProviderVdc constructs a specific struct for `compute_capacity` attribute in the `vcd_provider_vdc` Terraform state. +func getComputeCapacityForProviderVdc(computeCapacity *types.RootComputeCapacity) *[]map[string]interface{} { + cpuValueMap := map[string]interface{}{} + if computeCapacity.Cpu != nil { + cpuValueMap["allocation"] = computeCapacity.Cpu.Allocation + cpuValueMap["total"] = computeCapacity.Cpu.Total + cpuValueMap["overhead"] = computeCapacity.Cpu.Overhead + cpuValueMap["used"] = computeCapacity.Cpu.Used + cpuValueMap["units"] = computeCapacity.Cpu.Units + cpuValueMap["reserved"] = computeCapacity.Cpu.Reserved + } + memoryValueMap := map[string]interface{}{} + if computeCapacity.Memory != nil { + memoryValueMap["allocation"] = computeCapacity.Memory.Allocation + memoryValueMap["total"] = computeCapacity.Memory.Total + memoryValueMap["overhead"] = computeCapacity.Memory.Overhead + memoryValueMap["used"] = computeCapacity.Memory.Used + memoryValueMap["units"] = computeCapacity.Memory.Units + memoryValueMap["reserved"] = computeCapacity.Memory.Reserved + } + var memoryCapacityArray []map[string]interface{} + memoryCapacityArray = append(memoryCapacityArray, memoryValueMap) + var cpuCapacityArray []map[string]interface{} + cpuCapacityArray = append(cpuCapacityArray, cpuValueMap) + + rootInternal := map[string]interface{}{} + rootInternal["cpu"] = &cpuCapacityArray + rootInternal["memory"] = &memoryCapacityArray + rootInternal["is_elastic"] = computeCapacity.IsElastic + rootInternal["is_ha"] = computeCapacity.IsHA + + var root []map[string]interface{} + root = append(root, rootInternal) + return &root +} diff --git a/vcd/datasource_vcd_provider_vdc_test.go b/vcd/datasource_vcd_provider_vdc_test.go new file mode 100644 index 000000000..5a2f27317 --- /dev/null +++ b/vcd/datasource_vcd_provider_vdc_test.go @@ -0,0 +1,76 @@ +//go:build ALL || functional +// +build ALL functional + +package vcd + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "regexp" + "testing" +) + +func TestAccVcdDatasourceProviderVdc(t *testing.T) { + // Pre-checks + preTestChecks(t) + if !usingSysAdmin() { + t.Skip(t.Name() + " requires system admin privileges") + return + } + + // Test configuration + var params = StringMap{ + "ProviderVdcName": testConfig.VCD.NsxtProviderVdc.Name, + } + testParamsNotEmpty(t, params) + configText := templateFill(testAccVcdDatasourceProviderVdc, params) + debugPrintf("#[DEBUG] CONFIGURATION: %s", configText) + + if vcdShortTest { + t.Skip(acceptanceTestsSkipped) + return + } + + // Test cases + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: configText, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.vcd_provider_vdc.pvdc1", "name", params["ProviderVdcName"].(string)), + resource.TestMatchResourceAttr("data.vcd_provider_vdc.pvdc1", "id", getProviderVdcDatasourceAttributeUrnRegex("providervdc")), + resource.TestCheckResourceAttr("data.vcd_provider_vdc.pvdc1", "is_enabled", "true"), + resource.TestCheckResourceAttr("data.vcd_provider_vdc.pvdc1", "status", "1"), + resource.TestMatchResourceAttr("data.vcd_provider_vdc.pvdc1", "nsxt_manager_id", getProviderVdcDatasourceAttributeUrnRegex("nsxtmanager")), + resource.TestMatchResourceAttr("data.vcd_provider_vdc.pvdc1", "highest_supported_hardware_version", regexp.MustCompile(`vmx-[\d]+`)), + resource.TestCheckResourceAttr("data.vcd_provider_vdc.pvdc1", "compute_provider_scope", "vc1"), + resource.TestCheckResourceAttr("data.vcd_provider_vdc.pvdc1", "compute_capacity.0.cpu.0.units", "MHz"), + resource.TestCheckResourceAttr("data.vcd_provider_vdc.pvdc1", "compute_capacity.0.is_elastic", "false"), + resource.TestCheckResourceAttr("data.vcd_provider_vdc.pvdc1", "compute_capacity.0.is_ha", "false"), + resource.TestCheckResourceAttr("data.vcd_provider_vdc.pvdc1", "compute_capacity.0.memory.0.units", "MB"), + resource.TestMatchResourceAttr("data.vcd_provider_vdc.pvdc1", "external_network_ids.0", getProviderVdcDatasourceAttributeUrnRegex("network")), + resource.TestMatchResourceAttr("data.vcd_provider_vdc.pvdc1", "capabilities.0", regexp.MustCompile(`vmx-[\d]+`)), + resource.TestMatchResourceAttr("data.vcd_provider_vdc.pvdc1", "host_ids.0", getProviderVdcDatasourceAttributeUrnRegex("host")), + resource.TestMatchResourceAttr("data.vcd_provider_vdc.pvdc1", "network_pool_ids.0", getProviderVdcDatasourceAttributeUrnRegex("networkpool")), + resource.TestMatchResourceAttr("data.vcd_provider_vdc.pvdc1", "resource_pool_ids.0", getProviderVdcDatasourceAttributeUrnRegex("vimserver")), + resource.TestMatchResourceAttr("data.vcd_provider_vdc.pvdc1", "storage_container_ids.0", getProviderVdcDatasourceAttributeUrnRegex("vimserver")), + resource.TestMatchResourceAttr("data.vcd_provider_vdc.pvdc1", "storage_profile_ids.0", getProviderVdcDatasourceAttributeUrnRegex("providervdcstorageprofile")), + resource.TestMatchResourceAttr("data.vcd_provider_vdc.pvdc1", "vcenter_id", getProviderVdcDatasourceAttributeUrnRegex("vimserver")), + ), + }, + }, + }) + postTestChecks(t) +} + +// As the `vcd_provider_vdc` data source has a lot of URNs in its attributes, this function tries to centralize URN checking +// for this test case. +func getProviderVdcDatasourceAttributeUrnRegex(itemType string) *regexp.Regexp { + return regexp.MustCompile(`urn:vcloud:` + itemType + `:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`) +} + +const testAccVcdDatasourceProviderVdc = ` +data "vcd_provider_vdc" "pvdc1" { + name = "{{.ProviderVdcName}}" +} +` diff --git a/vcd/datasource_vcd_vm_group.go b/vcd/datasource_vcd_vm_group.go new file mode 100644 index 000000000..dcc2f4444 --- /dev/null +++ b/vcd/datasource_vcd_vm_group.go @@ -0,0 +1,68 @@ +package vcd + +import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "log" +) + +// datasourceVcdVmGroup defines the data source for a VM Group, used to create VM Placement Policies. +func datasourceVcdVmGroup() *schema.Resource { + return &schema.Resource{ + ReadContext: datasourceVcdVmGroupRead, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the VM Group to fetch", + }, + "provider_vdc_id": { + Type: schema.TypeString, + Required: true, + Description: "ID of the Provider VDC to which the VM Group to fetch belongs", + }, + "cluster_name": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the vSphere cluster associated to this VM Group", + }, + "named_vm_group_id": { + Type: schema.TypeString, + Computed: true, + Description: "ID of the named VM Group. Used to create Logical VM Groups", + }, + "vcenter_id": { + Type: schema.TypeString, + Computed: true, + Description: "ID of the vCenter server", + }, + "cluster_moref": { + Type: schema.TypeString, + Computed: true, + Description: "Managed object reference of the vSphere cluster associated to this VM Group", + }, + }, + } +} + +func datasourceVcdVmGroupRead(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + vcdClient := meta.(*VCDClient) + + name := d.Get("name").(string) + providerVdcId := d.Get("provider_vdc_id").(string) + + vmGroup, err := vcdClient.GetVmGroupByNameAndProviderVdcUrn(name, providerVdcId) + if err != nil { + log.Printf("[DEBUG] Could not find any VM Group with name %s and pVDC %s: %s", name, providerVdcId, err) + return diag.Errorf("could not find any VM Group with name %s and pVDC %s: %s", name, providerVdcId, err) + } + + dSet(d, "cluster_name", vmGroup.VmGroup.ClusterName) + dSet(d, "named_vm_group_id", vmGroup.VmGroup.NamedVmGroupId) + dSet(d, "vcenter_id", vmGroup.VmGroup.VcenterId) + dSet(d, "cluster_moref", vmGroup.VmGroup.ClusterMoref) + + d.SetId(vmGroup.VmGroup.ID) + return nil +} diff --git a/vcd/datasource_vcd_vm_group_test.go b/vcd/datasource_vcd_vm_group_test.go new file mode 100644 index 000000000..84658741b --- /dev/null +++ b/vcd/datasource_vcd_vm_group_test.go @@ -0,0 +1,63 @@ +//go:build ALL || functional +// +build ALL functional + +package vcd + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "regexp" + "testing" +) + +func TestAccVcdDatasourceVmGroup(t *testing.T) { + // Pre-checks + preTestChecks(t) + if !usingSysAdmin() { + t.Skip(t.Name() + " requires system admin privileges") + return + } + + // Test configuration + var params = StringMap{ + "ProviderVdcName": testConfig.VCD.NsxtProviderVdc.Name, + "VmGroup": testConfig.VCD.NsxtProviderVdc.PlacementPolicyVmGroup, + } + testParamsNotEmpty(t, params) + configText := templateFill(testAccVcdDatasourceVmGroup, params) + debugPrintf("#[DEBUG] CONFIGURATION: %s", configText) + + if vcdShortTest { + t.Skip(acceptanceTestsSkipped) + return + } + + // Test cases + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: configText, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair("data.vcd_vm_group.vm-group", "provider_vdc_id", "data.vcd_provider_vdc.pvdc1", "id"), + resource.TestCheckResourceAttrSet("data.vcd_vm_group.vm-group", "name"), + resource.TestCheckResourceAttrSet("data.vcd_vm_group.vm-group", "cluster_moref"), + resource.TestCheckResourceAttrSet("data.vcd_vm_group.vm-group", "cluster_name"), + resource.TestMatchResourceAttr("data.vcd_vm_group.vm-group", "vcenter_id", regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)), + resource.TestMatchResourceAttr("data.vcd_vm_group.vm-group", "named_vm_group_id", regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)), + ), + }, + }, + }) + postTestChecks(t) +} + +const testAccVcdDatasourceVmGroup = ` +data "vcd_provider_vdc" "pvdc1" { + name = "{{.ProviderVdcName}}" +} + +data "vcd_vm_group" "vm-group" { + name = "{{.VmGroup}}" + provider_vdc_id = data.vcd_provider_vdc.pvdc1.id +} +` diff --git a/vcd/datasource_vcd_vm_placement_policy.go b/vcd/datasource_vcd_vm_placement_policy.go new file mode 100644 index 000000000..4022b4493 --- /dev/null +++ b/vcd/datasource_vcd_vm_placement_policy.go @@ -0,0 +1,51 @@ +package vcd + +import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func datasourceVcdVmPlacementPolicy() *schema.Resource { + + return &schema.Resource{ + ReadContext: datasourceVcdVmPlacementPolicyRead, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the VM Placement Policy", + }, + "provider_vdc_id": { + Type: schema.TypeString, + Required: true, + Description: "ID of the Provider VDC to which the VM Placement Policy belongs", + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: "Description of the VM Placement Policy", + }, + "vm_group_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + Description: "IDs of the collection of VMs with similar host requirements", + }, + "logical_vm_group_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + Description: "IDs of one or more Logical VM Groups defined in this VM Placement policy. There is an AND relationship among all the entries fetched to this attribute", + }, + }, + } +} + +func datasourceVcdVmPlacementPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return sharedVcdVmPlacementPolicyRead(ctx, d, meta, false) +} diff --git a/vcd/provider.go b/vcd/provider.go index d5d1468a1..38365bb63 100644 --- a/vcd/provider.go +++ b/vcd/provider.go @@ -104,6 +104,9 @@ var globalDataSourceMap = map[string]*schema.Resource{ "vcd_nsxt_edgegateway_bgp_ip_prefix_list": datasourceVcdEdgeBgpIpPrefixList(), // 3.7 "vcd_nsxt_dynamic_security_group": datasourceVcdDynamicSecurityGroup(), // 3.7 "vcd_org_ldap": datasourceVcdOrgLdap(), // 3.8 + "vcd_vm_placement_policy": datasourceVcdVmPlacementPolicy(), // 3.8 + "vcd_provider_vdc": datasourceVcdProviderVdc(), // 3.8 + "vcd_vm_group": datasourceVcdVmGroup(), // 3.8 } var globalResourceMap = map[string]*schema.Resource{ @@ -178,6 +181,7 @@ var globalResourceMap = map[string]*schema.Resource{ "vcd_nsxt_edgegateway_bgp_ip_prefix_list": resourceVcdEdgeBgpIpPrefixList(), // 3.7 "vcd_nsxt_edgegateway_bgp_configuration": resourceVcdEdgeBgpConfig(), // 3.7 "vcd_org_ldap": resourceVcdOrgLdap(), // 3.8 + "vcd_vm_placement_policy": resourceVcdVmPlacementPolicy(), // 3.8 } // Provider returns a terraform.ResourceProvider. diff --git a/vcd/resource_vcd_org_vdc.go b/vcd/resource_vcd_org_vdc.go index 430c98304..5e933fcfd 100644 --- a/vcd/resource_vcd_org_vdc.go +++ b/vcd/resource_vcd_org_vdc.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "log" + "net/url" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -240,16 +241,34 @@ func resourceVcdOrgVdc() *schema.Resource { Type: schema.TypeSet, Optional: true, Computed: true, - Description: "Set of VM sizing policy IDs", + Description: "Set of VM Sizing Policy IDs", Elem: &schema.Schema{ Type: schema.TypeString, }, }, - "default_vm_sizing_policy_id": { - Type: schema.TypeString, + "vm_placement_policy_ids": { + Type: schema.TypeSet, Optional: true, Computed: true, - Description: "ID of default VM sizing policy ID", + Description: "Set of VM Placement Policy IDs", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "default_vm_sizing_policy_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "ID of default VM Compute policy, which can be a VM Sizing Policy, VM Placement Policy or vGPU Policy", + ConflictsWith: []string{"default_compute_policy_id"}, + Deprecated: "Use `default_compute_policy_id` attribute instead, which can support VM Sizing Policies, VM Placement Policies and vGPU Policies", + }, + "default_compute_policy_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "ID of default Compute policy for this VDC, which can be a VM Sizing Policy, VM Placement Policy or vGPU Policy", + ConflictsWith: []string{"default_vm_sizing_policy_id"}, }, }, } @@ -305,9 +324,9 @@ func resourceVcdVdcCreate(ctx context.Context, d *schema.ResourceData, meta inte return diag.Errorf("error adding metadata to VDC: %s", err) } - err = addAssignedVmSizingPolicies(vcdClient, d, meta) + err = addAssignedComputePolicies(d, meta) if err != nil { - return diag.Errorf("error assigning VM sizing policies to VDC: %s", err) + return diag.Errorf("error assigning VM Compute Policies to VDC: %s", err) } return resourceVcdVdcRead(ctx, d, meta) @@ -417,23 +436,37 @@ func setOrgVdcData(d *schema.ResourceData, vcdClient *VCDClient, adminOrg *govcd return fmt.Errorf("error setting metadata: %s", err) } - assignedVmSizingPolicies, err := adminVdc.GetAllAssignedVdcComputePolicies(nil) + dSet(d, "default_vm_sizing_policy_id", adminVdc.AdminVdc.DefaultComputePolicy.ID) // Deprecated, populating for compatibility + dSet(d, "default_compute_policy_id", adminVdc.AdminVdc.DefaultComputePolicy.ID) + + assignedVmComputePolicies, err := adminVdc.GetAllAssignedVdcComputePoliciesV2(url.Values{ + "filter": []string{"isVgpuPolicy==false;policyType==VdcVmPolicy"}, // Filtering out vGPU Policies as there's no attribute support yet. + }) if err != nil { - log.Printf("[DEBUG] Unable to get assigned VM sizing policies") - return fmt.Errorf("unable to get assigned VM sizing policies %s", err) - } - var policyIds []string - for _, policy := range assignedVmSizingPolicies { - policyIds = append(policyIds, policy.VdcComputePolicy.ID) + log.Printf("[DEBUG] Unable to get assigned VM Compute policies") + return fmt.Errorf("unable to get assigned VM Compute policies %s", err) + } + var sizingPolicyIds []string + var placementPolicyIds []string + for _, policy := range assignedVmComputePolicies { + if policy.VdcComputePolicyV2.IsSizingOnly { + sizingPolicyIds = append(sizingPolicyIds, policy.VdcComputePolicyV2.ID) + } else { + placementPolicyIds = append(placementPolicyIds, policy.VdcComputePolicyV2.ID) + } } - vmSizingPoliciesSet := convertStringsToTypeSet(policyIds) - dSet(d, "default_vm_sizing_policy_id", adminVdc.AdminVdc.DefaultComputePolicy.ID) + vmSizingPoliciesSet := convertStringsToTypeSet(sizingPolicyIds) + vmPlacementPoliciesSet := convertStringsToTypeSet(placementPolicyIds) err = d.Set("vm_sizing_policy_ids", vmSizingPoliciesSet) if err != nil { return err } + err = d.Set("vm_placement_policy_ids", vmPlacementPoliciesSet) + if err != nil { + return err + } log.Printf("[TRACE] vdc read completed: %#v", adminVdc.AdminVdc) return nil @@ -548,7 +581,7 @@ func resourceVcdVdcUpdate(ctx context.Context, d *schema.ResourceData, meta inte return diag.Errorf("error updating VDC metadata: %s", err) } - err = updateAssignedVmSizingPolicies(vcdClient, d, meta) + err = updateAssignedVmComputePolicies(d, meta, changedAdminVdc) if err != nil { return diag.Errorf("error assigning VM sizing policies to VDC: %s", err) } @@ -765,157 +798,172 @@ func resourceVcdVdcDelete(_ context.Context, d *schema.ResourceData, meta interf return nil } -// updateAssignedVmSizingPolicies handles VM sizing policies. -func updateAssignedVmSizingPolicies(vcdClient *VCDClient, d *schema.ResourceData, meta interface{}) error { - log.Printf("[TRACE] updating assigned VM sizing policies to VDC") - - _, idsOk := d.GetOk("vm_sizing_policy_ids") - _, defaultIdOk := d.GetOk("default_vm_sizing_policy_id") - - if idsOk != defaultIdOk { - return fmt.Errorf("both fields are required `vm_sizing_policy_ids` and `default_vm_sizing_policy_id`") - } - - // early return - if !d.HasChange("default_vm_sizing_policy_id") && !d.HasChange("vm_sizing_policy_ids") { - return nil - } - - vcdComputePolicyHref, err := vcdClient.Client.OpenApiBuildEndpoint(types.OpenApiPathVersion1_0_0, types.OpenApiEndpointVdcComputePolicies) +// updateAssignedVmComputePolicies handles VM compute policies. +func updateAssignedVmComputePolicies(d *schema.ResourceData, meta interface{}, vdc *govcd.AdminVdc) error { + vcdClient := meta.(*VCDClient) + defaultPolicyId, vcdComputePolicyHref, err := getDefaultPolicyIdAndComputePolicyHref(d, vcdClient) if err != nil { - return fmt.Errorf("error constructing HREF for compute policy") + return err } - - adminOrg, err := vcdClient.GetAdminOrgFromResource(d) - if err != nil { - return fmt.Errorf(errorRetrievingOrg, err) + if vcdComputePolicyHref == nil || defaultPolicyId == "" { + return nil } - vdc, err := adminOrg.GetAdminVDCByName(d.Get("name").(string), false) - if err != nil { - return fmt.Errorf(errorRetrievingVdcFromOrg, d.Get("org").(string), d.Get("name").(string), err) - } + arePoliciesChanged := d.HasChange("vm_sizing_policy_ids") || d.HasChange("vm_placement_policy_ids") + isDefaultPolicyChanged := d.HasChange("default_compute_policy_id") || d.HasChange("default_vm_sizing_policy_id") - if d.HasChange("default_vm_sizing_policy_id") && !d.HasChange("vm_sizing_policy_ids") { - defaultPolicyId := d.Get("default_vm_sizing_policy_id").(string) + // Compatibility patch: Remove deprecated `default_vm_sizing_policy_id` from this conditional when the attribute is removed. + // We can do this as `default_compute_policy_id` contains the same value as `default_vm_sizing_policy_id`. + if isDefaultPolicyChanged && !arePoliciesChanged { vdc.AdminVdc.DefaultComputePolicy = &types.Reference{HREF: vcdComputePolicyHref.String() + defaultPolicyId, ID: defaultPolicyId} _, err := vdc.Update() if err != nil { - return fmt.Errorf("error setting default VM sizing policy. %s", err) + return fmt.Errorf("error setting default VM Compute Policy. %s", err) } return nil } - if d.HasChange("vm_sizing_policy_ids") && !d.HasChange("default_vm_sizing_policy_id") { - vmSizingPolicyIdStrings := convertSchemaSetToSliceOfStrings(d.Get("vm_sizing_policy_ids").(*schema.Set)) - if !ifIdIsPartOfSlice(d.Get("default_vm_sizing_policy_id").(string), vmSizingPolicyIdStrings) { - return errors.New("`default_vm_sizing_policy_id` isn't part of `vm_sizing_policy_ids`") + if !isDefaultPolicyChanged && arePoliciesChanged { + var vmComputePolicyIds []string + computePolicyAttributes := []string{"vm_sizing_policy_ids", "vm_placement_policy_ids"} + for _, attribute := range computePolicyAttributes { + vmComputePolicyIds = append(vmComputePolicyIds, convertSchemaSetToSliceOfStrings(d.Get(attribute).(*schema.Set))...) } - policyReferences := types.VdcComputePolicyReferences{} + if !contains(vmComputePolicyIds, defaultPolicyId) { + return fmt.Errorf("`default_compute_policy_id` %s is not present in any of `%v`", defaultPolicyId, computePolicyAttributes) + } + var vdcComputePolicyReferenceList []*types.Reference - for _, policyId := range vmSizingPolicyIdStrings { + for _, policyId := range vmComputePolicyIds { vdcComputePolicyReferenceList = append(vdcComputePolicyReferenceList, &types.Reference{HREF: vcdComputePolicyHref.String() + policyId}) } - policyReferences.VdcComputePolicyReference = vdcComputePolicyReferenceList + policyReferences := types.VdcComputePolicyReferences{} + policyReferences.VdcComputePolicyReference = vdcComputePolicyReferenceList _, err = vdc.SetAssignedComputePolicies(policyReferences) if err != nil { - return fmt.Errorf("error setting VM sizing policies. %s", err) + return fmt.Errorf("error setting VM Compute Policies. %s", err) } return nil } - err = changeVmSizingPoliciesAndDefaultId(d, vcdComputePolicyHref.String(), vdc) + err = changeComputePoliciesAndDefaultId(d, vcdComputePolicyHref.String(), vdc) if err != nil { return err } return nil } -func ifIdIsPartOfSlice(id string, ids []string) bool { - if id == "" && len(ids) == 0 { - return true +// changeComputePoliciesAndDefaultId handles Compute policies. Created VDC generates default Compute policy which requires additional handling. +// Assigning and setting default Compute policies requires different API calls. Default policy can't be removed, as result +// we approach this with adding new policies, set new default, remove all old policies. +func changeComputePoliciesAndDefaultId(d *schema.ResourceData, vcdComputePolicyHref string, vdc *govcd.AdminVdc) error { + arePoliciesChanged := d.HasChange("vm_sizing_policy_ids") || d.HasChange("vm_placement_policy_ids") + isDefaultPolicyChanged := d.HasChange("default_compute_policy_id") || d.HasChange("default_vm_sizing_policy_id") + if !arePoliciesChanged && !isDefaultPolicyChanged { + return nil } - found := false - for _, idInSlice := range ids { - if id == idInSlice { - found = true - break - } + + // Deprecation compatibility: If `default_compute_policy_id` is not set, fallback to deprecated one. + // We can do this as `default_compute_policy_id` contains the same value as `default_vm_sizing_policy_id`. + defaultPolicyId, defaultPolicyIsSet := d.GetOk("default_compute_policy_id") + if !defaultPolicyIsSet { + defaultPolicyId, _ = d.GetOk("default_vm_sizing_policy_id") } - return found -} -// changeVmSizingPoliciesAndDefaultId handles VM sizing policies. Created VDC generates default VM sizing policy which requires additional handling. -// Assigning and setting default VM sizing policies requires different API calls. Default policy can't be removed, as result -// we approach this with adding new policies, set new default, remove all old policies. -func changeVmSizingPoliciesAndDefaultId(d *schema.ResourceData, vcdComputePolicyHref string, vdc *govcd.AdminVdc) error { - if d.HasChange("default_vm_sizing_policy_id") && d.HasChange("vm_sizing_policy_ids") { - vmSizingPolicyIdStrings := convertSchemaSetToSliceOfStrings(d.Get("vm_sizing_policy_ids").(*schema.Set)) - if !ifIdIsPartOfSlice(d.Get("default_vm_sizing_policy_id").(string), vmSizingPolicyIdStrings) { - return errors.New("`default_vm_sizing_policy_id` isn't part of `vm_sizing_policy_ids`") - } - policyReferences := types.VdcComputePolicyReferences{} - var vdcComputePolicyReferenceList []*types.Reference - for _, policyId := range vmSizingPolicyIdStrings { - vdcComputePolicyReferenceList = append(vdcComputePolicyReferenceList, &types.Reference{HREF: vcdComputePolicyHref + policyId}) - } + var vmComputePolicyIds []string + computePolicyAttributes := []string{"vm_sizing_policy_ids", "vm_placement_policy_ids"} + for _, attribute := range computePolicyAttributes { + vmComputePolicyIds = append(vmComputePolicyIds, convertSchemaSetToSliceOfStrings(d.Get(attribute).(*schema.Set))...) + } + if !contains(vmComputePolicyIds, defaultPolicyId.(string)) { + return fmt.Errorf("`default_compute_policy_id` %s is not present in any of `%v`", defaultPolicyId.(string), computePolicyAttributes) + } - existingPolicies, err := vdc.GetAllAssignedVdcComputePolicies(nil) - if err != nil { - return fmt.Errorf("error getting VM sizing policies. %s", err) - } - for _, existingPolicy := range existingPolicies { - vdcComputePolicyReferenceList = append(vdcComputePolicyReferenceList, &types.Reference{HREF: vcdComputePolicyHref + existingPolicy.VdcComputePolicy.ID}) - } + var vdcComputePolicyReferenceList []*types.Reference + for _, policyId := range vmComputePolicyIds { + vdcComputePolicyReferenceList = append(vdcComputePolicyReferenceList, &types.Reference{HREF: vcdComputePolicyHref + policyId}) + } - policyReferences.VdcComputePolicyReference = vdcComputePolicyReferenceList + existingPolicies, err := vdc.GetAllAssignedVdcComputePoliciesV2(url.Values{ + "filter": []string{"isVgpuPolicy==false;policyType==VdcVmPolicy"}, // Filtering out vGPU Policies as there's no attribute support yet. + }) + if err != nil { + return fmt.Errorf("error getting Compute Policies. %s", err) + } + for _, existingPolicy := range existingPolicies { + vdcComputePolicyReferenceList = append(vdcComputePolicyReferenceList, &types.Reference{HREF: vcdComputePolicyHref + existingPolicy.VdcComputePolicyV2.ID}) + } - _, err = vdc.SetAssignedComputePolicies(policyReferences) - if err != nil { - return fmt.Errorf("error setting VM sizing policies. %s", err) - } + policyReferences := types.VdcComputePolicyReferences{} + policyReferences.VdcComputePolicyReference = vdcComputePolicyReferenceList + _, err = vdc.SetAssignedComputePolicies(policyReferences) + if err != nil { + return fmt.Errorf("error setting Compute Policies. %s", err) + } - // set default VM sizing policy - defaultPolicyId := d.Get("default_vm_sizing_policy_id").(string) - vdc.AdminVdc.DefaultComputePolicy = &types.Reference{HREF: vcdComputePolicyHref + defaultPolicyId, ID: defaultPolicyId} - updatedVdc, err := vdc.Update() - if err != nil { - return fmt.Errorf("error setting default VM sizing policy. %s", err) - } + // set default Compute Policy + vdc.AdminVdc.DefaultComputePolicy = &types.Reference{HREF: vcdComputePolicyHref + defaultPolicyId.(string), ID: defaultPolicyId.(string)} + updatedVdc, err := vdc.Update() + if err != nil { + return fmt.Errorf("error setting default Compute Policy. %s", err) + } - // Now we can remove previously existing policies as default policy changed - vdcComputePolicyReferenceList = []*types.Reference{} - for _, policyId := range vmSizingPolicyIdStrings { - vdcComputePolicyReferenceList = append(vdcComputePolicyReferenceList, &types.Reference{HREF: vcdComputePolicyHref + policyId}) - } - policyReferences.VdcComputePolicyReference = vdcComputePolicyReferenceList + // Now we can remove previously existing policies as default policy changed + vdcComputePolicyReferenceList = []*types.Reference{} + for _, policyId := range vmComputePolicyIds { + vdcComputePolicyReferenceList = append(vdcComputePolicyReferenceList, &types.Reference{HREF: vcdComputePolicyHref + policyId}) + } + policyReferences.VdcComputePolicyReference = vdcComputePolicyReferenceList - _, err = updatedVdc.SetAssignedComputePolicies(policyReferences) - if err != nil { - return fmt.Errorf("error setting VM sizing policies. %s", err) - } + _, err = updatedVdc.SetAssignedComputePolicies(policyReferences) + if err != nil { + return fmt.Errorf("error setting Compute Policies. %s", err) } return nil } -// addAssignedVmSizingPolicies handles VM sizing policies. Created VDC generates default VM sizing policy which requires additional handling. -// Assigning and setting default VM sizing policies requires different API calls. Default approach is add new policies, set new default, remove all policies. -func addAssignedVmSizingPolicies(vcdClient *VCDClient, d *schema.ResourceData, meta interface{}) error { - log.Printf("[TRACE] updating assigned VM sizing policies to VDC") - _, idsOk := d.GetOk("vm_sizing_policy_ids") - _, defaultIdOk := d.GetOk("default_vm_sizing_policy_id") - if idsOk != defaultIdOk { - return fmt.Errorf("both fields are required `vm_sizing_policy_ids` and `default_vm_sizing_policy_id`") - } - // early return - if !d.HasChange("default_vm_sizing_policy_id") && !d.HasChange("vm_sizing_policy_ids") { - return nil +// getDefaultPolicyIdAndComputePolicyHref gets the default compute policy ID and returns the Compute Policy API endpoint. +func getDefaultPolicyIdAndComputePolicyHref(d *schema.ResourceData, vcdClient *VCDClient) (string, *url.URL, error) { + // Deprecation compatibility: If `default_compute_policy_id` is not set, fallback to deprecated one. + // We can do this as `default_compute_policy_id` contains the same value as `default_vm_sizing_policy_id`. + defaultPolicyId, defaultPolicyIsSet := d.GetOk("default_compute_policy_id") + if !defaultPolicyIsSet { + defaultPolicyId, defaultPolicyIsSet = d.GetOk("default_vm_sizing_policy_id") + } + + _, sizingOk := d.GetOk("vm_sizing_policy_ids") + _, placementOk := d.GetOk("vm_placement_policy_ids") + + if defaultPolicyIsSet && !sizingOk && !placementOk { + return "", nil, fmt.Errorf("when `default_compute_policy_id` is used, it requires also `vm_sizing_policy_ids` or `vm_placement_policy_ids`") } - vcdComputePolicyHref, err := vcdClient.Client.OpenApiBuildEndpoint(types.OpenApiPathVersion1_0_0, types.OpenApiEndpointVdcComputePolicies) + arePoliciesChanged := d.HasChange("vm_sizing_policy_ids") || d.HasChange("vm_placement_policy_ids") + isDefaultPolicyChanged := d.HasChange("default_compute_policy_id") || d.HasChange("default_vm_sizing_policy_id") + + // Early return + if !isDefaultPolicyChanged && !arePoliciesChanged { + return "", nil, nil + } + + vcdComputePolicyHref, err := vcdClient.Client.OpenApiBuildEndpoint(types.OpenApiPathVersion2_0_0, types.OpenApiEndpointVdcComputePolicies) if err != nil { - return fmt.Errorf("error constructing HREF for compute policy") + return "", nil, fmt.Errorf("error constructing HREF for Compute Policy") + } + return defaultPolicyId.(string), vcdComputePolicyHref, nil +} + +// addAssignedComputePolicies handles Compute policies. Created VDC generates default Compute policy which requires additional handling. +// Assigning and setting default Compute policies requires different API calls. Default approach is add new policies, set new default, remove all policies. +func addAssignedComputePolicies(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + _, vcdComputePolicyHref, err := getDefaultPolicyIdAndComputePolicyHref(d, vcdClient) + if err != nil { + return err + } + if vcdComputePolicyHref == nil { + return nil } adminOrg, err := vcdClient.GetAdminOrgFromResource(d) @@ -928,11 +976,10 @@ func addAssignedVmSizingPolicies(vcdClient *VCDClient, d *schema.ResourceData, m return fmt.Errorf(errorRetrievingVdcFromOrg, d.Get("org").(string), d.Get("name").(string), err) } - err = changeVmSizingPoliciesAndDefaultId(d, vcdComputePolicyHref.String(), vdc) + err = changeComputePoliciesAndDefaultId(d, vcdComputePolicyHref.String(), vdc) if err != nil { return err } - return nil } diff --git a/vcd/resource_vcd_org_vdc_with_all_compute_policies_test.go b/vcd/resource_vcd_org_vdc_with_all_compute_policies_test.go new file mode 100644 index 000000000..141d44e25 --- /dev/null +++ b/vcd/resource_vcd_org_vdc_with_all_compute_policies_test.go @@ -0,0 +1,240 @@ +//go:build vdc || ALL || functional +// +build vdc ALL functional + +package vcd + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccVcdOrgVdcWithAllComputePolicies(t *testing.T) { + preTestChecks(t) + if !usingSysAdmin() { + t.Skip(t.Name() + " requires system admin privileges") + } + + var params = StringMap{ + "VmGroup": testConfig.VCD.NsxtProviderVdc.PlacementPolicyVmGroup, + "VdcName": t.Name(), + "OrgName": testConfig.VCD.Org, + "ProviderVdc": testConfig.VCD.NsxtProviderVdc.Name, + "NetworkPool": testConfig.VCD.NsxtProviderVdc.NetworkPool, + "ProviderVdcStorageProfile": testConfig.VCD.NsxtProviderVdc.StorageProfile, + "FuncName": t.Name(), + } + testParamsNotEmpty(t, params) + + resourceName := "vcd_org_vdc." + t.Name() + configText := templateFill(testAccCheckVcdVdcAllComputePolicies, params) + params["FuncName"] = t.Name() + "-Update" + configText2 := templateFill(testAccCheckVcdVdcAllComputePolicies_update, params) + debugPrintf("#[DEBUG] CONFIGURATION - creation: %s", configText) + + if vcdShortTest { + t.Skip(acceptanceTestsSkipped) + return + } + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviders, + CheckDestroy: resource.ComposeAggregateTestCheckFunc( + testAccCheckVdcDestroy, + testAccCheckComputePolicyDestroyed("placement1", "placement"), + testAccCheckComputePolicyDestroyed("sizing1", "sizing"), + testAccCheckComputePolicyDestroyed("sizing2", "sizing"), + ), + Steps: []resource.TestStep{ + { + Config: configText, + Check: resource.ComposeTestCheckFunc( + testAccCheckVcdVdcExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", t.Name()), + resource.TestCheckResourceAttr(resourceName, "org", testConfig.VCD.Org), + resource.TestCheckResourceAttr(resourceName, "network_pool_name", testConfig.VCD.NsxtProviderVdc.NetworkPool), + resource.TestCheckResourceAttr(resourceName, "provider_vdc_name", testConfig.VCD.NsxtProviderVdc.Name), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "storage_profile.0.name", testConfig.VCD.NsxtProviderVdc.StorageProfile), + resource.TestCheckResourceAttrPair(resourceName, "default_compute_policy_id", "vcd_vm_placement_policy.placement1", "id"), + resource.TestCheckResourceAttr(resourceName, "vm_placement_policy_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "vm_sizing_policy_ids.#", "1"), + ), + }, + { + Config: configText2, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "default_compute_policy_id", "vcd_vm_placement_policy.placement1", "id"), + resource.TestCheckResourceAttr(resourceName, "vm_placement_policy_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "vm_sizing_policy_ids.#", "2"), + ), + }, + }, + }) + postTestChecks(t) +} + +const testAccCheckVcdVdcAllComputePolicies = ` +data "vcd_provider_vdc" "pvdc" { + name = "{{.ProviderVdc}}" +} + +data "vcd_vm_group" "vm-group" { + name = "{{.VmGroup}}" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id +} + +resource "vcd_vm_placement_policy" "placement1" { + name = "placement1" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + description = "placement1 description" + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +resource "vcd_vm_sizing_policy" "sizing1" { + name = "sizing1" + description = "sizing1 description" + cpu { + shares = "886" + limit_in_mhz = "12375" + count = "9" + speed_in_mhz = "2500" + cores_per_socket = "3" + reservation_guarantee = "0.55" + } +} + +resource "vcd_vm_sizing_policy" "sizing2" { + name = "sizing2" + description = "sizing2 description" + cpu { + shares = "886" + limit_in_mhz = "12375" + count = "9" + speed_in_mhz = "2500" + cores_per_socket = "3" + reservation_guarantee = "0.55" + } +} + +resource "vcd_org_vdc" "{{.VdcName}}" { + name = "{{.VdcName}}" + org = "{{.OrgName}}" + + allocation_model = "ReservationPool" + network_pool_name = "{{.NetworkPool}}" + provider_vdc_name = data.vcd_provider_vdc.pvdc.name + + compute_capacity { + cpu { + allocated = 1024 + limit = 1024 + } + + memory { + allocated = 1024 + limit = 1024 + } + } + + storage_profile { + name = "{{.ProviderVdcStorageProfile}}" + enabled = true + limit = 10240 + default = true + } + + enabled = true + enable_thin_provisioning = true + enable_fast_provisioning = true + delete_force = true + delete_recursive = true + + default_compute_policy_id = vcd_vm_placement_policy.placement1.id + vm_placement_policy_ids = [vcd_vm_placement_policy.placement1.id] + vm_sizing_policy_ids = [vcd_vm_sizing_policy.sizing1.id] +} +` + +// Here we change assignments of the policies to test the Update operation +const testAccCheckVcdVdcAllComputePolicies_update = ` +data "vcd_provider_vdc" "pvdc" { + name = "{{.ProviderVdc}}" +} + +data "vcd_vm_group" "vm-group" { + name = "{{.VmGroup}}" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id +} + +resource "vcd_vm_placement_policy" "placement1" { + name = "placement1" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + description = "placement1 description" + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +resource "vcd_vm_sizing_policy" "sizing1" { + name = "sizing1" + description = "sizing1 description" + cpu { + shares = "886" + limit_in_mhz = "12375" + count = "9" + speed_in_mhz = "2500" + cores_per_socket = "3" + reservation_guarantee = "0.55" + } +} + +resource "vcd_vm_sizing_policy" "sizing2" { + name = "sizing2" + description = "sizing2 description" + cpu { + shares = "886" + limit_in_mhz = "12375" + count = "9" + speed_in_mhz = "2500" + cores_per_socket = "3" + reservation_guarantee = "0.55" + } +} + +resource "vcd_org_vdc" "{{.VdcName}}" { + name = "{{.VdcName}}" + org = "{{.OrgName}}" + + allocation_model = "ReservationPool" + network_pool_name = "{{.NetworkPool}}" + provider_vdc_name = data.vcd_provider_vdc.pvdc.name + + compute_capacity { + cpu { + allocated = 1024 + limit = 1024 + } + + memory { + allocated = 1024 + limit = 1024 + } + } + + storage_profile { + name = "{{.ProviderVdcStorageProfile}}" + enabled = true + limit = 10240 + default = true + } + + enabled = true + enable_thin_provisioning = true + enable_fast_provisioning = true + delete_force = true + delete_recursive = true + + default_compute_policy_id = vcd_vm_placement_policy.placement1.id + vm_placement_policy_ids = [vcd_vm_placement_policy.placement1.id] + vm_sizing_policy_ids = [vcd_vm_sizing_policy.sizing1.id, vcd_vm_sizing_policy.sizing2.id] +} +` diff --git a/vcd/resource_vcd_org_vdc_with_vm_placement_policy_test.go b/vcd/resource_vcd_org_vdc_with_vm_placement_policy_test.go new file mode 100644 index 000000000..74f9ebdd6 --- /dev/null +++ b/vcd/resource_vcd_org_vdc_with_vm_placement_policy_test.go @@ -0,0 +1,312 @@ +//go:build vdc || ALL || functional +// +build vdc ALL functional + +package vcd + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccVcdOrgVdcWithVmPlacementPolicy(t *testing.T) { + preTestChecks(t) + if !usingSysAdmin() { + t.Skip(t.Name() + " requires system admin privileges") + } + if testConfig.VCD.NsxtProviderVdc.Name == "" { + t.Skip("Variable nsxtProviderVdc.Name must be set to run VDC tests") + } + + var params = StringMap{ + "VmGroup": testConfig.VCD.NsxtProviderVdc.PlacementPolicyVmGroup, + "VdcName": t.Name(), + "OrgName": testConfig.VCD.Org, + "ProviderVdc": testConfig.VCD.NsxtProviderVdc.Name, + "NetworkPool": testConfig.VCD.NsxtProviderVdc.NetworkPool, + "ProviderVdcStorageProfile": testConfig.VCD.NsxtProviderVdc.StorageProfile, + "FuncName": t.Name(), + } + testParamsNotEmpty(t, params) + + resourceName := "vcd_org_vdc." + t.Name() + configText := templateFill(testAccCheckVcdVdcVmPlacementPolicies_basic, params) + params["FuncName"] = t.Name() + "-Update" + updateText := templateFill(testAccCheckVcdVdcVmPlacementPolicies_update, params) + params["FuncName"] = t.Name() + "-Update2" + updateText2 := templateFill(testAccCheckVcdVdcVmPlacementPolicies_update2, params) + + debugPrintf("#[DEBUG] CONFIGURATION - creation: %s", configText) + debugPrintf("#[DEBUG] CONFIGURATION - update: %s", updateText) + + if vcdShortTest { + t.Skip(acceptanceTestsSkipped) + return + } + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviders, + CheckDestroy: resource.ComposeAggregateTestCheckFunc( + testAccCheckVdcDestroy, + testAccCheckComputePolicyDestroyed("placement1", "placement"), + testAccCheckComputePolicyDestroyed("placement2", "placement"), + testAccCheckComputePolicyDestroyed("placement3", "placement"), + ), + Steps: []resource.TestStep{ + { + Config: configText, + Check: resource.ComposeTestCheckFunc( + testAccCheckVcdVdcExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", t.Name()), + resource.TestCheckResourceAttr(resourceName, "org", testConfig.VCD.Org), + resource.TestCheckResourceAttr(resourceName, "network_pool_name", testConfig.VCD.NsxtProviderVdc.NetworkPool), + resource.TestCheckResourceAttr(resourceName, "provider_vdc_name", testConfig.VCD.NsxtProviderVdc.Name), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "storage_profile.0.name", testConfig.VCD.NsxtProviderVdc.StorageProfile), + resource.TestCheckResourceAttrPair(resourceName, "default_compute_policy_id", "vcd_vm_placement_policy.placement1", "id"), + resource.TestCheckResourceAttr(resourceName, "vm_placement_policy_ids.#", "3"), + ), + }, + { + Config: updateText, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "default_compute_policy_id", "vcd_vm_placement_policy.placement3", "id"), + resource.TestCheckResourceAttr(resourceName, "vm_placement_policy_ids.#", "2"), + ), + }, + { + Config: updateText2, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "default_compute_policy_id", "vcd_vm_sizing_policy.dummy", "id"), + resource.TestCheckResourceAttr(resourceName, "vm_sizing_policy_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "vm_placement_policy_ids.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: importStateIdOrgObject(t.Name()), + // These fields can't be retrieved + ImportStateVerifyIgnore: []string{"delete_force", "delete_recursive"}, + }, + }, + }) + postTestChecks(t) +} + +const testAccCheckVcdVdcVmPlacementPolicies_basic = ` +data "vcd_provider_vdc" "pvdc" { + name = "{{.ProviderVdc}}" +} + +data "vcd_vm_group" "vm-group" { + name = "{{.VmGroup}}" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id +} + +resource "vcd_vm_placement_policy" "placement1" { + name = "placement1" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + description = "placement1 description" + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +resource "vcd_vm_placement_policy" "placement2" { + name = "placement2" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + description = "placement2 description" + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +resource "vcd_vm_placement_policy" "placement3" { + name = "placement3" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + description = "placement3 description" + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +resource "vcd_org_vdc" "{{.VdcName}}" { + name = "{{.VdcName}}" + org = "{{.OrgName}}" + + allocation_model = "ReservationPool" + network_pool_name = "{{.NetworkPool}}" + provider_vdc_name = data.vcd_provider_vdc.pvdc.name + + compute_capacity { + cpu { + allocated = 1024 + limit = 1024 + } + + memory { + allocated = 1024 + limit = 1024 + } + } + + storage_profile { + name = "{{.ProviderVdcStorageProfile}}" + enabled = true + limit = 10240 + default = true + } + + enabled = true + enable_thin_provisioning = true + enable_fast_provisioning = true + delete_force = true + delete_recursive = true + + default_compute_policy_id = vcd_vm_placement_policy.placement1.id + vm_placement_policy_ids = [vcd_vm_placement_policy.placement1.id, vcd_vm_placement_policy.placement2.id, vcd_vm_placement_policy.placement3.id] +} +` + +const testAccCheckVcdVdcVmPlacementPolicies_update = ` +# skip-binary-test: only for updates +data "vcd_provider_vdc" "pvdc" { + name = "{{.ProviderVdc}}" +} + +data "vcd_vm_group" "vm-group" { + name = "{{.VmGroup}}" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id +} + +resource "vcd_vm_placement_policy" "placement1" { + name = "placement1" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + description = "placement1 description" + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +resource "vcd_vm_placement_policy" "placement2" { + name = "placement2" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + description = "placement2 description" + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +resource "vcd_vm_placement_policy" "placement3" { + name = "placement3" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + description = "placement3 description" + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +resource "vcd_org_vdc" "{{.VdcName}}" { + name = "{{.VdcName}}" + org = "{{.OrgName}}" + + allocation_model = "ReservationPool" + network_pool_name = "{{.NetworkPool}}" + provider_vdc_name = data.vcd_provider_vdc.pvdc.name + + compute_capacity { + cpu { + allocated = 1024 + limit = 1024 + } + + memory { + allocated = 1024 + limit = 1024 + } + } + + storage_profile { + name = "{{.ProviderVdcStorageProfile}}" + enabled = true + limit = 10240 + default = true + } + + enabled = true + enable_thin_provisioning = true + enable_fast_provisioning = true + delete_force = true + delete_recursive = true + + default_compute_policy_id = vcd_vm_placement_policy.placement3.id + vm_placement_policy_ids = [vcd_vm_placement_policy.placement1.id, vcd_vm_placement_policy.placement3.id] +} +` + +// In this one we remove the VM Placement Policies +const testAccCheckVcdVdcVmPlacementPolicies_update2 = ` +# skip-binary-test: only for updates +data "vcd_provider_vdc" "pvdc" { + name = "{{.ProviderVdc}}" +} + +data "vcd_vm_group" "vm-group" { + name = "{{.VmGroup}}" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id +} + +resource "vcd_vm_sizing_policy" "dummy" { + name = "dummy" + description = "dummy description" +} + +resource "vcd_vm_placement_policy" "placement1" { + name = "placement1" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + description = "placement1 description" + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +resource "vcd_vm_placement_policy" "placement2" { + name = "placement2" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + description = "placement2 description" + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +resource "vcd_vm_placement_policy" "placement3" { + name = "placement3" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + description = "placement3 description" + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +resource "vcd_org_vdc" "{{.VdcName}}" { + name = "{{.VdcName}}" + org = "{{.OrgName}}" + + allocation_model = "ReservationPool" + network_pool_name = "{{.NetworkPool}}" + provider_vdc_name = data.vcd_provider_vdc.pvdc.name + + compute_capacity { + cpu { + allocated = 1024 + limit = 1024 + } + + memory { + allocated = 1024 + limit = 1024 + } + } + + storage_profile { + name = "{{.ProviderVdcStorageProfile}}" + enabled = true + limit = 10240 + default = true + } + + enabled = true + enable_thin_provisioning = true + enable_fast_provisioning = true + delete_force = true + delete_recursive = true + + default_compute_policy_id = vcd_vm_sizing_policy.dummy.id # As this can't be empty + vm_sizing_policy_ids = [vcd_vm_sizing_policy.dummy.id] + vm_placement_policy_ids = [] +} +` diff --git a/vcd/resource_vcd_org_vdc_with_vm_sizing_policy_test.go b/vcd/resource_vcd_org_vdc_with_vm_sizing_policy_test.go index 3553cc223..0aef51cdd 100644 --- a/vcd/resource_vcd_org_vdc_with_vm_sizing_policy_test.go +++ b/vcd/resource_vcd_org_vdc_with_vm_sizing_policy_test.go @@ -49,16 +49,24 @@ func TestAccVcdOrgVdcWithVmSizingPolicy(t *testing.T) { configText := templateFill(testAccCheckVcdVdcVmSizingPolicies_basic, params) params["FuncName"] = t.Name() + "-Update" updateText := templateFill(testAccCheckVcdVdcVmSizingPolicies_update, params) + params["FuncName"] = t.Name() + "-Update2" + updateText2 := templateFill(testAccCheckVcdVdcVmSizingPolicies_update2, params) if vcdShortTest { t.Skip(acceptanceTestsSkipped) return } debugPrintf("#[DEBUG] CONFIGURATION - creation: %s", configText) debugPrintf("#[DEBUG] CONFIGURATION - update: %s", updateText) + debugPrintf("#[DEBUG] CONFIGURATION - update with deprecated `default_vm_sizing_policy_id`: %s", updateText2) resource.Test(t, resource.TestCase{ ProviderFactories: testAccProviders, - CheckDestroy: testAccCheckVdcDestroy, + CheckDestroy: resource.ComposeAggregateTestCheckFunc( + testAccCheckVdcDestroy, + testAccCheckComputePolicyDestroyed("min-size", "sizing"), + testAccCheckComputePolicyDestroyed("min-size2", "sizing"), + testAccCheckComputePolicyDestroyed("min-size3", "sizing"), + ), Steps: []resource.TestStep{ { Config: configText, @@ -115,7 +123,7 @@ func TestAccVcdOrgVdcWithVmSizingPolicy(t *testing.T) { resource.TestMatchResourceAttr( "vcd_org_vdc."+TestAccVcdVdc, "include_vm_memory_overhead", regexp.MustCompile(`^`+params["MemoryOverheadValueForAssert"].(string)+`$`)), - resource.TestCheckResourceAttrPair("vcd_org_vdc."+TestAccVcdVdc, "default_vm_sizing_policy_id", + resource.TestCheckResourceAttrPair("vcd_org_vdc."+TestAccVcdVdc, "default_compute_policy_id", "vcd_vm_sizing_policy.minSize3", "id"), resource.TestCheckResourceAttr("vcd_org_vdc."+TestAccVcdVdc, "vm_sizing_policy_ids.#", "3"), @@ -176,12 +184,21 @@ func TestAccVcdOrgVdcWithVmSizingPolicy(t *testing.T) { resource.TestMatchResourceAttr( "vcd_org_vdc."+TestAccVcdVdc, "include_vm_memory_overhead", regexp.MustCompile(`^`+params["MemoryOverheadValueForAssert"].(string)+`$`)), - resource.TestCheckResourceAttrPair("vcd_org_vdc."+TestAccVcdVdc, "default_vm_sizing_policy_id", + resource.TestCheckResourceAttrPair("vcd_org_vdc."+TestAccVcdVdc, "default_compute_policy_id", "vcd_vm_sizing_policy.minSize2", "id"), resource.TestCheckResourceAttr("vcd_org_vdc."+TestAccVcdVdc, "vm_sizing_policy_ids.#", "1"), ), }, + { + Config: updateText2, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair("vcd_org_vdc."+TestAccVcdVdc, "default_vm_sizing_policy_id", + "vcd_vm_sizing_policy.minSize2", "id"), + resource.TestCheckResourceAttrPair("vcd_org_vdc."+TestAccVcdVdc, "default_vm_sizing_policy_id", + "vcd_org_vdc."+TestAccVcdVdc, "default_compute_policy_id"), + ), + }, { ResourceName: "vcd_org_vdc." + TestAccVcdVdc, ImportState: true, @@ -280,8 +297,8 @@ resource "vcd_org_vdc" "{{.VdcName}}" { {{.FlexElasticKey}} {{.equalsChar}} {{.FlexElasticValue}} {{.FlexMemoryOverheadKey}} {{.equalsChar}} {{.FlexMemoryOverheadValue}} - default_vm_sizing_policy_id = vcd_vm_sizing_policy.minSize3.id - vm_sizing_policy_ids = [vcd_vm_sizing_policy.minSize.id, vcd_vm_sizing_policy.minSize2.id,vcd_vm_sizing_policy.minSize3.id] + default_compute_policy_id = vcd_vm_sizing_policy.minSize3.id + vm_sizing_policy_ids = [vcd_vm_sizing_policy.minSize.id, vcd_vm_sizing_policy.minSize2.id,vcd_vm_sizing_policy.minSize3.id] } ` @@ -328,6 +345,94 @@ resource "vcd_vm_sizing_policy" "minSize3" { } } +resource "vcd_org_vdc" "{{.VdcName}}" { + name = "{{.VdcName}}" + org = "{{.OrgName}}" + + allocation_model = "{{.AllocationModel}}" + network_pool_name = "{{.NetworkPool}}" + provider_vdc_name = "{{.ProviderVdc}}" + + compute_capacity { + cpu { + allocated = "{{.Allocated}}" + limit = "{{.Limit}}" + } + + memory { + allocated = "{{.Allocated}}" + limit = "{{.Limit}}" + } + } + + storage_profile { + name = "{{.ProviderVdcStorageProfile}}" + enabled = true + limit = 10240 + default = true + } + + metadata = { + vdc_metadata = "VDC Metadata" + } + + enabled = true + enable_thin_provisioning = true + enable_fast_provisioning = true + delete_force = true + delete_recursive = true + {{.FlexElasticKey}} {{.equalsChar}} {{.FlexElasticValue}} + {{.FlexMemoryOverheadKey}} {{.equalsChar}} {{.FlexMemoryOverheadValue}} + + default_compute_policy_id = vcd_vm_sizing_policy.minSize2.id + vm_sizing_policy_ids = [vcd_vm_sizing_policy.minSize2.id] +} +` + +// Deprecated: Remove once `default_vm_sizing_policy_id` is removed. +const testAccCheckVcdVdcVmSizingPolicies_update2 = ` +# skip-binary-test: only for updates +resource "vcd_vm_sizing_policy" "minSize" { + name = "min-size" + description = "smallest size" +} + +resource "vcd_vm_sizing_policy" "minSize2" { + name = "min-size2" + description = "smallest size2" + + cpu { + shares = "886" + limit_in_mhz = "12375" + count = "9" + speed_in_mhz = "2500" + cores_per_socket = "3" + reservation_guarantee = "0.55" + } + +} + +resource "vcd_vm_sizing_policy" "minSize3" { + name = "min-size3" + description = "smallest size2" + + cpu { + shares = "886" + limit_in_mhz = "12375" + count = "9" + speed_in_mhz = "2500" + cores_per_socket = "3" + reservation_guarantee = "0.55" + } + + memory { + shares = "1580" + size_in_mb = "3200" + limit_in_mb = "2800" + reservation_guarantee = "0.3" + } +} + resource "vcd_org_vdc" "{{.VdcName}}" { name = "{{.VdcName}}" org = "{{.OrgName}}" diff --git a/vcd/resource_vcd_standalone_vm_with_vm_sizing_test.go b/vcd/resource_vcd_standalone_vm_with_vm_sizing_test.go index 4cd55f786..602127b9b 100644 --- a/vcd/resource_vcd_standalone_vm_with_vm_sizing_test.go +++ b/vcd/resource_vcd_standalone_vm_with_vm_sizing_test.go @@ -315,7 +315,7 @@ resource "vcd_org_vdc" "{{.VdcName}}" { {{.FlexElasticKey}} {{.equalsChar}} {{.FlexElasticValue}} {{.FlexMemoryOverheadKey}} {{.equalsChar}} {{.FlexMemoryOverheadValue}} - default_vm_sizing_policy_id = vcd_vm_sizing_policy.size_full.id + default_compute_policy_id = vcd_vm_sizing_policy.size_full.id vm_sizing_policy_ids = [vcd_vm_sizing_policy.minSize.id, vcd_vm_sizing_policy.size_cpu.id,vcd_vm_sizing_policy.size_full.id] } ` diff --git a/vcd/resource_vcd_vapp_vm_with_vm_sizing_test.go b/vcd/resource_vcd_vapp_vm_with_vm_sizing_test.go index fbf8ba470..751f8878c 100644 --- a/vcd/resource_vcd_vapp_vm_with_vm_sizing_test.go +++ b/vcd/resource_vcd_vapp_vm_with_vm_sizing_test.go @@ -382,7 +382,7 @@ resource "vcd_org_vdc" "{{.VdcName}}" { {{.FlexElasticKey}} {{.equalsChar}} {{.FlexElasticValue}} {{.FlexMemoryOverheadKey}} {{.equalsChar}} {{.FlexMemoryOverheadValue}} - default_vm_sizing_policy_id = vcd_vm_sizing_policy.size_full.id + default_compute_policy_id = vcd_vm_sizing_policy.size_full.id vm_sizing_policy_ids = [vcd_vm_sizing_policy.minSize.id, vcd_vm_sizing_policy.size_cpu.id,vcd_vm_sizing_policy.size_memory.id,vcd_vm_sizing_policy.size_full.id] } diff --git a/vcd/resource_vcd_vm_placement_policy.go b/vcd/resource_vcd_vm_placement_policy.go new file mode 100644 index 000000000..7f8cac41d --- /dev/null +++ b/vcd/resource_vcd_vm_placement_policy.go @@ -0,0 +1,441 @@ +package vcd + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/vmware/go-vcloud-director/v2/util" + "log" + "net/url" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/go-vcloud-director/v2/govcd" + "github.com/vmware/go-vcloud-director/v2/types/v56" +) + +func resourceVcdVmPlacementPolicy() *schema.Resource { + + return &schema.Resource{ + CreateContext: resourceVmPlacementPolicyCreate, + ReadContext: resourceVmPlacementPolicyRead, + UpdateContext: resourceVmPlacementPolicyUpdate, + DeleteContext: resourceVmPlacementPolicyDelete, + Importer: &schema.ResourceImporter{ + StateContext: resourceVmPlacementPolicyImport, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the VM Placement Policy", + }, + "provider_vdc_id": { + Type: schema.TypeString, + Required: true, + Description: "ID of the Provider VDC to which the VM Placement Policy belongs", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Description of the VM Placement Policy", + }, + "vm_group_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + Description: "IDs of the collection of VMs with similar host requirements", + AtLeastOneOf: []string{"vm_group_ids", "logical_vm_group_ids"}, + }, + "logical_vm_group_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + Description: "IDs of one or more Logical VM Groups to define this VM Placement Policy. There is an AND relationship among all the entries set in this attribute", + AtLeastOneOf: []string{"vm_group_ids", "logical_vm_group_ids"}, + }, + }, + } +} + +func resourceVmPlacementPolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + vmGroupIds := d.Get("vm_group_ids").(*schema.Set) + logicalVmGroupIds := d.Get("logical_vm_group_ids").(*schema.Set) + if len(vmGroupIds.List()) == 0 && len(logicalVmGroupIds.List()) == 0 { + return diag.Errorf("either `vm_group_ids` or `logical_vm_group_ids` must have a") + } + + log.Printf("[TRACE] VM Placement Policy creation initiated: %s in pVDC %s", d.Get("name").(string), d.Get("provider_vdc_id").(string)) + vcdClient := meta.(*VCDClient) + + if !vcdClient.Client.IsSysAdmin { + return diag.Errorf("functionality requires System administrator privileges") + } + + pVdc, err := vcdClient.GetProviderVdcById(d.Get("provider_vdc_id").(string)) + if err != nil { + return diag.Errorf("could not retrieve required Provider VDC: %s", err) + } + + computePolicy := &types.VdcComputePolicyV2{ + VdcComputePolicy: types.VdcComputePolicy{ + Name: d.Get("name").(string), + Description: getStringAttributeAsPointer(d, "description"), + IsSizingOnly: false, + }, + PolicyType: "VdcVmPolicy", + } + + vmGroups, err := getPvdcNamedVmGroupsMap(d, vcdClient, pVdc) + if err != nil { + return diag.FromErr(err) + } + computePolicy.PvdcNamedVmGroupsMap = vmGroups + + logicalVmGroups, err := getPvdcLogicalVmGroupsMap(d, vcdClient, pVdc) + if err != nil { + return diag.FromErr(err) + } + computePolicy.PvdcLogicalVmGroupsMap = logicalVmGroups + + log.Printf("[DEBUG] Creating VM Placement Policy: %#v", computePolicy) + + createdVmSizingPolicy, err := vcdClient.CreateVdcComputePolicyV2(computePolicy) + if err != nil { + log.Printf("[DEBUG] Error creating VM Placement Policy: %s", err) + return diag.Errorf("error creating VM Placement Policy: %s", err) + } + + d.SetId(createdVmSizingPolicy.VdcComputePolicyV2.ID) + log.Printf("[TRACE] VM Placement Policy created: %#v", createdVmSizingPolicy.VdcComputePolicyV2) + + return sharedVcdVmPlacementPolicyRead(ctx, d, meta, true) +} + +func resourceVmPlacementPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return sharedVcdVmPlacementPolicyRead(ctx, d, meta, true) +} + +// sharedVcdVmPlacementPolicyRead is a Read function shared between this resource and the corresponding data source. +func sharedVcdVmPlacementPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}, isResource bool) diag.Diagnostics { + policyName := d.Get("name").(string) + pVdcId := d.Get("provider_vdc_id").(string) + log.Printf("[TRACE] VM Placement Policy read initiated: %s in pVDC with ID %s", policyName, pVdcId) + + vcdClient := meta.(*VCDClient) + + // The method variable stores the information about how we found the rule, for logging purposes + method := "id" + + var policy *govcd.VdcComputePolicyV2 + var err error + if d.Id() != "" { + policy, err = vcdClient.GetVdcComputePolicyV2ById(d.Id()) + if err != nil { + if isResource && govcd.ContainsNotFound(err) { + d.SetId("") + return nil + } + return diag.Errorf("unable to find VM Placement Policy %s: %s", policyName, err) + } + } + + // The secondary method of retrieval is from name + if d.Id() == "" { + if policyName == "" { + return diag.Errorf("both Placement Policy name and ID are empty") + } + if pVdcId == "" { + return diag.Errorf("both Provider VDC ID and Placement Policy ID are empty") + } + + method = "name" + queryParams := url.Values{} + queryParams.Add("filter", fmt.Sprintf("name==%s;pvdcId==%s", policyName, pVdcId)) + filteredPoliciesByName, err := vcdClient.GetAllVdcComputePoliciesV2(queryParams) + if err != nil { + log.Printf("[DEBUG] Unable to find VM Placement Policy %s of Provider VDC %s. Removing from tfstate.", policyName, pVdcId) + d.SetId("") + return diag.Errorf("unable to find VM Placement Policy %s of Provider VDC %s, err: %s. Removing from tfstate", policyName, pVdcId, err) + } + if len(filteredPoliciesByName) != 1 { + log.Printf("[DEBUG] Unable to find VM Placement Policy %s of Provider VDC %s. Found Policies by name: %d. Removing from tfstate.", policyName, pVdcId, len(filteredPoliciesByName)) + d.SetId("") + return diag.Errorf("[DEBUG] Unable to find VM Placement Policy %s of Provider VDC %s, err: %s. Found Policies by name: %d. Removing from tfstate", policyName, pVdcId, govcd.ErrorEntityNotFound, len(filteredPoliciesByName)) + } + policy = filteredPoliciesByName[0] + d.SetId(policy.VdcComputePolicyV2.ID) + } + + if policy == nil { + return diag.Errorf("[datasourceVcdVmPlacementPolicyRead] error defining VM Placement Policy") + } + util.Logger.Printf("[TRACE] [get VM Placement Policy] Retrieved by %s\n", method) + return setVmPlacementPolicy(ctx, d, vcdClient, *policy.VdcComputePolicyV2) +} + +func resourceVmPlacementPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + policyName := d.Get("name").(string) + log.Printf("[TRACE] VM sizing policy update initiated: %s", policyName) + + vcdClient := meta.(*VCDClient) + + policy, err := vcdClient.GetVdcComputePolicyV2ById(d.Id()) + if err != nil { + log.Printf("[DEBUG] Unable to find VM Placement Policy %s", policyName) + return diag.Errorf("unable to find VM Placement Policy %s, error: %s", policyName, err) + } + + if d.HasChange("name") { + policy.VdcComputePolicyV2.Name = d.Get("name").(string) + } + + if d.HasChange("description") { + policy.VdcComputePolicyV2.Description = getStringAttributeAsPointer(d, "description") + } + + if d.HasChange("vm_group_ids") { + vmGroups, err := getPvdcNamedVmGroupsMap(d, vcdClient, nil) + if err != nil { + return diag.FromErr(err) + } + policy.VdcComputePolicyV2.PvdcNamedVmGroupsMap = vmGroups + } + if d.HasChange("logical_vm_group_ids") { + logicalVmGroups, err := getPvdcLogicalVmGroupsMap(d, vcdClient, nil) + if err != nil { + return diag.FromErr(err) + } + policy.VdcComputePolicyV2.PvdcLogicalVmGroupsMap = logicalVmGroups + } + + _, err = policy.Update() + if err != nil { + log.Printf("[DEBUG] Error updating VM Placement Policy %s with error %s", policyName, err) + return diag.Errorf("error updating VM Placement Policy %s, err: %s", policyName, err) + } + + log.Printf("[TRACE] VM Placement Policy update completed: %s", policyName) + return resourceVmPlacementPolicyRead(ctx, d, meta) +} + +func resourceVmPlacementPolicyDelete(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + policyName := d.Get("name").(string) + log.Printf("[TRACE] VM Placement Policy delete started: %s", policyName) + + vcdClient := meta.(*VCDClient) + + if !vcdClient.Client.IsSysAdmin { + return diag.Errorf("functionality requires System administrator privileges") + } + + policy, err := vcdClient.GetVdcComputePolicyV2ById(d.Id()) + if err != nil { + log.Printf("[DEBUG] Unable to find VM Placement Policy %s. Removing from tfstate", policyName) + d.SetId("") + return nil + } + + err = policy.Delete() + if err != nil { + log.Printf("[DEBUG] Error deleting VM Placement Policy %s, err: %s", policyName, err) + return diag.Errorf("error deleting VM Placement Policy %s, err: %s", policyName, err) + } + + log.Printf("[TRACE] VM Placement Policy delete completed: %s", policyName) + return nil +} + +var errHelpVmPlacementPolicyImport = fmt.Errorf(`resource id must be specified in one of these formats: +'vm-placement-policy-name', 'vm-placement-policy-id' or 'list@' to get a list of VM placement policies with their IDs`) + +// resourceVmPlacementPolicyImport is responsible for importing the resource. +// The following steps happen as part of import +// 1. The user supplies `terraform import _resource_name_ _the_id_string_` command +// 2. `_the_id_string_` contains a dot formatted path to resource as in the example below +// 3. The functions splits the dot-formatted path and tries to lookup the object +// 4. If the lookup succeeds it set's the ID field for `_resource_name_` resource in state file +// (the resource must be already defined in .tf config otherwise `terraform import` will complain) +// 5. `terraform refresh` is being implicitly launched. The Read method looks up all other fields +// based on the known ID of object. +// +// Example resource name (_resource_name_): vcd_vm_placement_policy.my_existing_policy_name +// Example import path (_the_id_string_): my_existing_vm_placement_policy_id +// Example list path (_the_id_string_): list@ +// Note: the separator can be changed using Provider.import_separator or variable VCD_IMPORT_SEPARATOR +func resourceVmPlacementPolicyImport(_ context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + resourceURI := strings.Split(d.Id(), ImportSeparator) + + log.Printf("[DEBUG] importing VM Placement Policy resource with provided id %s", d.Id()) + + if len(resourceURI) != 1 { + return nil, errHelpVmPlacementPolicyImport + } + if strings.Contains(d.Id(), "list@") { + + return listComputePoliciesForImport(meta, "vcd_vm_placement_policy", "placement") + } else { + policyId := resourceURI[0] + return getVmPlacementPolicy(d, meta, policyId) + } +} + +// getPvdcNamedVmGroupsMap fetches the vm_group_ids attribute and retrieves the associated []types.PvdcNamedVmGroupsMap +func getPvdcNamedVmGroupsMap(d *schema.ResourceData, vcdClient *VCDClient, pVdc *govcd.ProviderVdc) ([]types.PvdcNamedVmGroupsMap, error) { + vmGroupIdsSet, isPopulated := d.GetOk("vm_group_ids") + if !isPopulated { + return []types.PvdcNamedVmGroupsMap{}, nil + } + + vmGroupIdsList := vmGroupIdsSet.(*schema.Set).List() + if len(vmGroupIdsList) == 0 { + return []types.PvdcNamedVmGroupsMap{}, nil + } + + // It is assumed that is a single-item list as there's one pVDC at a time: + pvdcNamedVmGroupsMap := []types.PvdcNamedVmGroupsMap{ + { + NamedVmGroups: []types.OpenApiReferences{{}}, + Pvdc: types.OpenApiReference{ + Name: pVdc.ProviderVdc.Name, + ID: pVdc.ProviderVdc.ID, + }, + }, + } + for _, vmGroupId := range vmGroupIdsList { + vmGroup, err := vcdClient.GetVmGroupById(vmGroupId.(string)) + if err != nil { + return nil, fmt.Errorf("error retrieving the associated name of VM Group %s: %s", vmGroupId, err) + } + // VM Placement policies use Named VM ID, not the normal ID + pvdcNamedVmGroupsMap[0].NamedVmGroups[0] = append(pvdcNamedVmGroupsMap[0].NamedVmGroups[0], types.OpenApiReference{ + ID: fmt.Sprintf("urn:vcloud:namedVmGroup:%s", vmGroup.VmGroup.NamedVmGroupId), + Name: vmGroup.VmGroup.Name, + }) + } + return pvdcNamedVmGroupsMap, nil +} + +// getPvdcLogicalVmGroupsMap fetches the logical_vm_group_ids attribute and retrieves the associated []types.PvdcLogicalVmGroupsMap +func getPvdcLogicalVmGroupsMap(d *schema.ResourceData, vcdClient *VCDClient, pVdc *govcd.ProviderVdc) ([]types.PvdcLogicalVmGroupsMap, error) { + vmGroupIdsSet, isPopulated := d.GetOk("logical_vm_group_ids") + if !isPopulated { + return []types.PvdcLogicalVmGroupsMap{}, nil + } + + vmGroupIdsList := vmGroupIdsSet.(*schema.Set).List() + if len(vmGroupIdsList) == 0 { + return []types.PvdcLogicalVmGroupsMap{}, nil + } + + // It is assumed that is a single-item list as there's one pVDC at a time: + logicalVmGroupReferences := []types.PvdcLogicalVmGroupsMap{ + { + LogicalVmGroups: types.OpenApiReferences{}, + Pvdc: types.OpenApiReference{ + Name: pVdc.ProviderVdc.Name, + ID: pVdc.ProviderVdc.ID, + }, + }, + } + for _, vmGroupId := range vmGroupIdsList { + logicalVmGroup, err := vcdClient.GetLogicalVmGroupById(vmGroupId.(string)) + if err != nil { + return nil, fmt.Errorf("error retrieving the associated name of Logical VM Group %s: %s", vmGroupId, err) + } + // It is assumed that is a single-item list as there's one pVDC at a time: + logicalVmGroupReferences[0].LogicalVmGroups = append(logicalVmGroupReferences[0].LogicalVmGroups, types.OpenApiReference{ + ID: vmGroupId.(string), + Name: logicalVmGroup.LogicalVmGroup.Name, + }) + } + return logicalVmGroupReferences, nil +} + +// getVmPlacementPolicy reads the corresponding VM Placement Policy from the resource. +func getVmPlacementPolicy(d *schema.ResourceData, meta interface{}, policyId string) ([]*schema.ResourceData, error) { + vcdClient := meta.(*VCDClient) + + var computePolicy *govcd.VdcComputePolicyV2 + var err error + computePolicy, err = vcdClient.GetVdcComputePolicyV2ById(policyId) + if err != nil { + queryParams := url.Values{} + queryParams.Add("filter", fmt.Sprintf("name==%s;isSizingOnly==false;isVgpuPolicy==false", policyId)) + computePolicies, err := vcdClient.GetAllVdcComputePoliciesV2(queryParams) + if err != nil { + log.Printf("[DEBUG] Unable to find VM Placement Policy %s", policyId) + return nil, fmt.Errorf("unable to find VM Placement Policy %s, err: %s", policyId, err) + } + if len(computePolicies) != 1 { + log.Printf("[DEBUG] Unable to find unique VM Placement Policy %s", policyId) + return nil, fmt.Errorf("unable to find unique VM Placement Policy %s, err: %s", policyId, err) + } + computePolicy = computePolicies[0] + } + + dSet(d, "name", computePolicy.VdcComputePolicyV2.Name) + dSet(d, "provider_vdc_id", computePolicy.VdcComputePolicyV2.PvdcID) + var vmGroupIds []string + for _, pvdcNamedVmGroupsMap := range computePolicy.VdcComputePolicyV2.PvdcNamedVmGroupsMap { + for _, namedVmGroups := range pvdcNamedVmGroupsMap.NamedVmGroups { + for _, namedVmGroup := range namedVmGroups { + vmGroupIds = append(vmGroupIds, namedVmGroup.ID) + } + } + } + if err = d.Set("vm_group_ids", vmGroupIds); err != nil { + return nil, fmt.Errorf("error setting vm_group_ids: %s", err) + } + + vmGroupIds = []string{} + for _, pvdcLogicalVmGroupsMap := range computePolicy.VdcComputePolicyV2.PvdcLogicalVmGroupsMap { + for _, logicalVmGroup := range pvdcLogicalVmGroupsMap.LogicalVmGroups { + vmGroupIds = append(vmGroupIds, logicalVmGroup.ID) + } + } + if err = d.Set("logical_vm_group_ids", vmGroupIds); err != nil { + return nil, fmt.Errorf("error setting logical_vm_group_ids: %s", err) + } + + d.SetId(computePolicy.VdcComputePolicyV2.ID) + + return []*schema.ResourceData{d}, nil +} + +// setVmPlacementPolicy sets the Terraform state from the Compute Policy input parameter +func setVmPlacementPolicy(_ context.Context, d *schema.ResourceData, vcdClient *VCDClient, policy types.VdcComputePolicyV2) diag.Diagnostics { + dSet(d, "name", policy.Name) + dSet(d, "description", policy.Description) + + var vmGroupIds []string + + for _, namedVmGroupPerPvdc := range policy.NamedVMGroups { + for _, namedVmGroup := range namedVmGroupPerPvdc { + // The Policy has "Named VM Group IDs" in its attributes, but we need "VM Group IDs" which are unique + vmGroup, err := vcdClient.VCDClient.GetVmGroupByNamedVmGroupIdAndProviderVdcUrn(namedVmGroup.ID, policy.PvdcID) + if err != nil { + return diag.Errorf("could not get VM Group associated to Named VM Group ID %s", namedVmGroup.ID) + } + vmGroupIds = append(vmGroupIds, vmGroup.VmGroup.ID) + } + } + if err := d.Set("vm_group_ids", vmGroupIds); err != nil { + return diag.Errorf("error setting vm_group_ids: %s", err) + } + + vmGroupIds = []string{} + for _, namedVmGroup := range policy.LogicalVMGroupReferences { + vmGroupIds = append(vmGroupIds, namedVmGroup.ID) + } + if err := d.Set("logical_vm_group_ids", vmGroupIds); err != nil { + return diag.Errorf("error setting logical_vm_group_ids: %s", err) + } + + log.Printf("[TRACE] VM Placement Policy read completed: %s", policy.Name) + return nil +} diff --git a/vcd/resource_vcd_vm_placement_policy_test.go b/vcd/resource_vcd_vm_placement_policy_test.go new file mode 100644 index 000000000..7885e61d7 --- /dev/null +++ b/vcd/resource_vcd_vm_placement_policy_test.go @@ -0,0 +1,192 @@ +//go:build vdc || ALL || functional +// +build vdc ALL functional + +package vcd + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccVcdVmPlacementPolicy(t *testing.T) { + preTestChecks(t) + if !usingSysAdmin() { + t.Skip(t.Name() + " requires system admin privileges") + } + if testConfig.VCD.ProviderVdc.Name == "" { + t.Skip("Variable providerVdc.Name must be set to run VDC tests") + } + + var params = StringMap{ + "PvdcName": testConfig.VCD.NsxtProviderVdc.Name, + "PolicyName": t.Name(), + "VmGroup": testConfig.VCD.NsxtProviderVdc.PlacementPolicyVmGroup, + "Description": t.Name() + "_description", + } + testParamsNotEmpty(t, params) + policyName := "vcd_vm_placement_policy." + params["PolicyName"].(string) + datasourcePolicyName := "data.vcd_vm_placement_policy.data-" + params["PolicyName"].(string) + configText := templateFill(testAccCheckVmPlacementPolicy_create, params) + params["FuncName"] = t.Name() + "-Update" + configTextUpdate := templateFill(testAccCheckVmPlacementPolicy_update, params) + + debugPrintf("#[DEBUG] CONFIGURATION - creation: %s", configText) + + if vcdShortTest { + t.Skip(acceptanceTestsSkipped) + return + } + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckComputePolicyDestroyed(t.Name()+"-update", "placement"), + Steps: []resource.TestStep{ + { + Config: configText, + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr(policyName, "id", regexp.MustCompile(`urn:vcloud:vdcComputePolicy:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)), + resource.TestCheckResourceAttr(policyName, "name", params["PolicyName"].(string)), + resource.TestCheckResourceAttr(policyName, "description", params["Description"].(string)), + resource.TestMatchResourceAttr(policyName, "provider_vdc_id", regexp.MustCompile(`urn:vcloud:providervdc:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)), + resource.TestCheckResourceAttr(policyName, "vm_group_ids.#", "1"), + resource.TestMatchResourceAttr(policyName, "vm_group_ids.0", regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)), + resourceFieldsEqual(policyName, datasourcePolicyName, nil), + ), + }, + { + Config: configTextUpdate, + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr(policyName, "id", regexp.MustCompile(`urn:vcloud:vdcComputePolicy:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)), + resource.TestCheckResourceAttr(policyName, "name", params["PolicyName"].(string)+"-update"), + resource.TestCheckResourceAttr(policyName, "description", params["Description"].(string)+"-update"), + resource.TestMatchResourceAttr(policyName, "provider_vdc_id", regexp.MustCompile(`urn:vcloud:providervdc:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)), + resource.TestCheckResourceAttr(policyName, "vm_group_ids.#", "1"), + resource.TestMatchResourceAttr(policyName, "vm_group_ids.0", regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)), + resourceFieldsEqual(policyName, datasourcePolicyName, nil), + ), + }, + // Tests import by id + { + ResourceName: policyName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: importStateComputePolicyByIdOrName(policyName, true), + }, + // Tests import by name + { + ResourceName: policyName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: importStateComputePolicyByIdOrName(policyName, false), + }, + }, + }) + postTestChecks(t) +} + +const testAccCheckVmPlacementPolicy_create = ` +data "vcd_provider_vdc" "pvdc" { + name = "{{.PvdcName}}" +} + +data "vcd_vm_group" "vm-group" { + name = "{{.VmGroup}}" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id +} + +resource "vcd_vm_placement_policy" "{{.PolicyName}}" { + name = "{{.PolicyName}}" + description = "{{.Description}}" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +data "vcd_vm_placement_policy" "data-{{.PolicyName}}" { + name = vcd_vm_placement_policy.{{.PolicyName}}.name + provider_vdc_id = vcd_vm_placement_policy.{{.PolicyName}}.provider_vdc_id +} +` + +const testAccCheckVmPlacementPolicy_update = ` +data "vcd_provider_vdc" "pvdc" { + name = "{{.PvdcName}}" +} + +data "vcd_vm_group" "vm-group" { + name = "{{.VmGroup}}" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id +} + +resource "vcd_vm_placement_policy" "{{.PolicyName}}" { + name = "{{.PolicyName}}-update" + description = "{{.Description}}-update" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} + +data "vcd_vm_placement_policy" "data-{{.PolicyName}}" { + name = vcd_vm_placement_policy.{{.PolicyName}}.name + provider_vdc_id = vcd_vm_placement_policy.{{.PolicyName}}.provider_vdc_id +} +` + +// TestAccVcdVmPlacementPolicyWithoutDescription checks that a VM Placement Policy without description specified in the HCL +// corresponds to a VM Placement Policy with an empty description in VCD. +func TestAccVcdVmPlacementPolicyWithoutDescription(t *testing.T) { + preTestChecks(t) + if !usingSysAdmin() { + t.Skip(t.Name() + " requires system admin privileges") + } + if testConfig.VCD.ProviderVdc.Name == "" { + t.Skip("Variable providerVdc.Name must be set to run VDC tests") + } + + var params = StringMap{ + "PvdcName": testConfig.VCD.NsxtProviderVdc.Name, + "PolicyName": t.Name(), + "VmGroup": testConfig.VCD.NsxtProviderVdc.PlacementPolicyVmGroup, + } + testParamsNotEmpty(t, params) + policyName := "vcd_vm_placement_policy." + params["PolicyName"].(string) + configText := templateFill(testAccCheckVmPlacementPolicyWithoutDescription, params) + + debugPrintf("#[DEBUG] CONFIGURATION - creation: %s", configText) + + if vcdShortTest { + t.Skip(acceptanceTestsSkipped) + return + } + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckComputePolicyDestroyed(t.Name(), "placement"), + Steps: []resource.TestStep{ + { + Config: configText, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(policyName, "description", ""), + ), + }, + }, + }) + postTestChecks(t) +} + +const testAccCheckVmPlacementPolicyWithoutDescription = ` +data "vcd_provider_vdc" "pvdc" { + name = "{{.PvdcName}}" +} + +data "vcd_vm_group" "vm-group" { + name = "{{.VmGroup}}" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id +} + +resource "vcd_vm_placement_policy" "{{.PolicyName}}" { + name = "{{.PolicyName}}" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + vm_group_ids = [ data.vcd_vm_group.vm-group.id ] +} +` diff --git a/vcd/resource_vcd_vm_sizing_policy.go b/vcd/resource_vcd_vm_sizing_policy.go index c7efb1b6d..5ce6462ed 100644 --- a/vcd/resource_vcd_vm_sizing_policy.go +++ b/vcd/resource_vcd_vm_sizing_policy.go @@ -165,7 +165,7 @@ func resourceVmSizingPolicyCreate(ctx context.Context, d *schema.ResourceData, m return resourceVmSizingPolicyRead(ctx, d, meta) } -// resourceVcdVmAffinityRuleRead reads a resource VM affinity rule +// resourceVmSizingPolicyRead reads a resource VM Sizing Policy func resourceVmSizingPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { return genericVcdVmSizingPolicyRead(ctx, d, meta) } @@ -198,7 +198,7 @@ func genericVcdVmSizingPolicyRead(ctx context.Context, d *schema.ResourceData, m } method = "name" queryParams := url.Values{} - queryParams.Add("filter", "name=="+policyName) + queryParams.Add("filter", fmt.Sprintf("name==%s;isSizingOnly==true", policyName)) filteredPoliciesByName, err := vcdClient.Client.GetAllVdcComputePolicies(queryParams) if err != nil { log.Printf("[DEBUG] Unable to find VM sizing policy %s. Removing from tfstate.", policyName) @@ -362,7 +362,7 @@ func getUpdatedVmSizingPolicyInput(d *schema.ResourceData, policy *govcd.VdcComp } if d.HasChange("description") { - policy.VdcComputePolicy.Description = d.Get("description").(string) + policy.VdcComputePolicy.Description = getStringAttributeAsPointer(d, "description") } if d.HasChange("cpu") { @@ -381,7 +381,7 @@ func getVmSizingPolicyInput(d *schema.ResourceData) (*types.VdcComputePolicy, er params := &types.VdcComputePolicy{ Name: d.Get("name").(string), - Description: d.Get("description").(string), + Description: getStringAttributeAsPointer(d, "description"), } cpuPart := d.Get("cpu").([]interface{}) @@ -522,7 +522,7 @@ func resourceVmSizingPolicyImport(_ context.Context, d *schema.ResourceData, met } if strings.Contains(d.Id(), "list@") { - return listVmSizingPoliciesForImport(meta) + return listComputePoliciesForImport(meta, "vcd_vm_sizing_policy", "sizing") } else { policyId := resourceURI[0] return getVmSizingPolicy(d, meta, policyId) @@ -532,71 +532,86 @@ func resourceVmSizingPolicyImport(_ context.Context, d *schema.ResourceData, met func getVmSizingPolicy(d *schema.ResourceData, meta interface{}, policyId string) ([]*schema.ResourceData, error) { vcdClient := meta.(*VCDClient) - var vmSizingPolicy *govcd.VdcComputePolicy + var computePolicy *govcd.VdcComputePolicy var err error - vmSizingPolicy, err = vcdClient.Client.GetVdcComputePolicyById(policyId) + computePolicy, err = vcdClient.Client.GetVdcComputePolicyById(policyId) if err != nil { queryParams := url.Values{} - queryParams.Add("filter", "name=="+policyId) - vmSizingPolicies, err := vcdClient.Client.GetAllVdcComputePolicies(queryParams) + queryParams.Add("filter", fmt.Sprintf("name==%s;isSizingOnly==true", policyId)) + computePolicies, err := vcdClient.Client.GetAllVdcComputePolicies(queryParams) if err != nil { - log.Printf("[DEBUG] Unable to find VM sizing policy %s", policyId) - return nil, fmt.Errorf("unable to find VM sizing policy %s, err: %s", policyId, err) + log.Printf("[DEBUG] Unable to find VM Sizing Policy %s", policyId) + return nil, fmt.Errorf("unable to find VM Sizing Policy %s, err: %s", policyId, err) } - if len(vmSizingPolicies) != 1 { - log.Printf("[DEBUG] Unable to find unique VM sizing policy %s", policyId) - return nil, fmt.Errorf("unable to find unique VM sizing policy %s, err: %s", policyId, err) + if len(computePolicies) != 1 { + log.Printf("[DEBUG] Unable to find unique VM Sizing Policy %s", policyId) + return nil, fmt.Errorf("unable to find unique VM Sizing Policy %s, err: %s", policyId, err) } - vmSizingPolicy = vmSizingPolicies[0] + computePolicy = computePolicies[0] } - dSet(d, "name", vmSizingPolicy.VdcComputePolicy.Name) - d.SetId(vmSizingPolicy.VdcComputePolicy.ID) + dSet(d, "name", computePolicy.VdcComputePolicy.Name) + d.SetId(computePolicy.VdcComputePolicy.ID) return []*schema.ResourceData{d}, nil } -func listVmSizingPoliciesForImport(meta interface{}) ([]*schema.ResourceData, error) { +func listComputePoliciesForImport(meta interface{}, origin, policyType string) ([]*schema.ResourceData, error) { vcdClient := meta.(*VCDClient) var err error buf := new(bytes.Buffer) - _, err = fmt.Fprintln(buf, "Retrieving all VM sizing policies") + _, err = fmt.Fprintln(buf, "Retrieving all "+policyType+" policies") if err != nil { - logForScreen("vcd_vm_sizing_policy", fmt.Sprintf("error writing to buffer: %s", err)) - } - policies, err := vcdClient.Client.GetAllVdcComputePolicies(nil) + logForScreen(origin, fmt.Sprintf("error writing to buffer: %s", err)) + } + queryParams := url.Values{} + filter := "isAutoGenerated==false;" // If we don't skip the auto generated policies, we also get in the list the ones that are created and assigned to a VDC by default + switch policyType { + case "sizing": + filter += "isSizingOnly==true" + case "placement": + filter += "isVgpuPolicy==false;isSizingOnly==false" + case "vgpu": + filter += "isVgpuPolicy==true" + default: + return nil, fmt.Errorf("unrecognized type of policy to import: %s", policyType) + } + queryParams.Add("filter", filter) + + policies, err := vcdClient.VCDClient.GetAllVdcComputePoliciesV2(queryParams) if err != nil { - return nil, fmt.Errorf("unable to retrieve VM sizing policies: %s", err) + return nil, fmt.Errorf("unable to retrieve %s policies: %s", policyType, err) } writer := tabwriter.NewWriter(buf, 0, 8, 1, '\t', tabwriter.AlignRight) _, err = fmt.Fprintln(writer, "No\tID\tName\t") if err != nil { - logForScreen("vcd_vm_sizing_policy", fmt.Sprintf("error writing to buffer: %s", err)) + logForScreen(origin, fmt.Sprintf("error writing to buffer: %s", err)) } _, err = fmt.Fprintln(writer, "--\t--\t----\t") if err != nil { - logForScreen("vcd_vm_sizing_policy", fmt.Sprintf("error writing to buffer: %s", err)) + logForScreen(origin, fmt.Sprintf("error writing to buffer: %s", err)) } for index, policy := range policies { - // If we don't skip the auto generated policies, we also get in the list the ones that are - // created and assigned to a VDC by default - if policy.VdcComputePolicy.IsAutoGenerated { - continue - } - _, err = fmt.Fprintf(writer, "%d\t%s\t%s \n", index+1, policy.VdcComputePolicy.ID, policy.VdcComputePolicy.Name) + _, err = fmt.Fprintf(writer, "%d\t%s\t%s \n", index+1, policy.VdcComputePolicyV2.ID, policy.VdcComputePolicyV2.Name) if err != nil { - logForScreen("vcd_vm_sizing_policy", fmt.Sprintf("error writing to buffer: %s", err)) + logForScreen(origin, fmt.Sprintf("error writing to buffer: %s", err)) } } err = writer.Flush() if err != nil { - logForScreen("vcd_vm_sizing_policy", fmt.Sprintf("error flushing buffer: %s", err)) + logForScreen(origin, fmt.Sprintf("error flushing buffer: %s", err)) + } + + switch policyType { + case "sizing": + return nil, fmt.Errorf("resource was not imported! %s\n%s", errHelpVmSizingPolicyImport, buf.String()) + default: + return nil, fmt.Errorf("resource was not imported! %s\n%s", errHelpVmSizingPolicyImport, buf.String()) } - return nil, fmt.Errorf("resource was not imported! %s\n%s", errHelpVmSizingPolicyImport, buf.String()) } diff --git a/vcd/resource_vcd_vm_sizing_policy_test.go b/vcd/resource_vcd_vm_sizing_policy_test.go index 06535fd84..327115c9a 100644 --- a/vcd/resource_vcd_vm_sizing_policy_test.go +++ b/vcd/resource_vcd_vm_sizing_policy_test.go @@ -60,7 +60,12 @@ func TestAccVcdVmSizingPolicy(t *testing.T) { resource4 := "vcd_vm_sizing_policy." + params["PolicyName"].(string) + "_4" resource.Test(t, resource.TestCase{ ProviderFactories: testAccProviders, - CheckDestroy: testAccCheckVmSizingPolicyDestroyed, + CheckDestroy: resource.ComposeAggregateTestCheckFunc( + testAccCheckComputePolicyDestroyed(params["PolicyName"].(string)+"_1", "sizing"), + testAccCheckComputePolicyDestroyed(params["PolicyName"].(string)+"_2", "sizing"), + testAccCheckComputePolicyDestroyed(params["PolicyName"].(string)+"_3", "sizing"), + testAccCheckComputePolicyDestroyed(params["PolicyName"].(string)+"_updated", "sizing"), + ), Steps: []resource.TestStep{ { Config: configText, @@ -155,14 +160,14 @@ func TestAccVcdVmSizingPolicy(t *testing.T) { ResourceName: resource4, ImportState: true, ImportStateVerify: true, - ImportStateIdFunc: importStateVmSizingPolicyByIdOrName(resource4, true), + ImportStateIdFunc: importStateComputePolicyByIdOrName(resource4, true), }, // Tests import by name { ResourceName: resource4, ImportState: true, ImportStateVerify: true, - ImportStateIdFunc: importStateVmSizingPolicyByIdOrName(resource4, false), + ImportStateIdFunc: importStateComputePolicyByIdOrName(resource4, false), }, { Config: dataSourceText, @@ -187,7 +192,7 @@ func TestAccVcdVmSizingPolicy(t *testing.T) { postTestChecks(t) } -func importStateVmSizingPolicyByIdOrName(resourceName string, byId bool) resource.ImportStateIdFunc { +func importStateComputePolicyByIdOrName(resourceName string, byId bool) resource.ImportStateIdFunc { return func(s *terraform.State) (string, error) { rs, ok := s.RootModule().Resources[resourceName] if !ok { @@ -225,22 +230,31 @@ func testAccCheckVmSizingPolicyExists(name string) resource.TestCheckFunc { } } -func testAccCheckVmSizingPolicyDestroyed(s *terraform.State) error { - conn := testAccProvider.Meta().(*VCDClient) - var err error - for _, rs := range s.RootModule().Resources { - if rs.Type != "vcd_org_vdc" && rs.Primary.Attributes["name"] != TestVmPolicy { - continue +// Checks that a VM Sizing Policy or a VM Placement Policy (depending on `policyType=sizing` or `policyType=placement`) +// is deleted from VCD after a Terraform destroy. +func testAccCheckComputePolicyDestroyed(policyName, policyType string) resource.TestCheckFunc { + resourceType := "vcd_vm_" + policyType + "_policy" + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*VCDClient) + var err error + var id string + for _, rs := range s.RootModule().Resources { + if rs.Type == resourceType && rs.Primary.Attributes["name"] == policyName { + id = rs.Primary.ID + } } - _, err = conn.Client.GetVdcComputePolicyById(rs.Primary.ID) + if id == "" { + return fmt.Errorf("%s with name %s was not found in tfstate", resourceType, policyName) + } + _, err = conn.GetVdcComputePolicyV2ById(id) if err == nil { - return fmt.Errorf("VM sizing policy %s still exists", rs.Primary.ID) + return fmt.Errorf("VM %s policy %s still exists", policyType, id) } - } - return nil + return nil + } } func init() { diff --git a/vcd/sample_vcd_test_config.json b/vcd/sample_vcd_test_config.json index 3b2042f35..631a650b1 100644 --- a/vcd/sample_vcd_test_config.json +++ b/vcd/sample_vcd_test_config.json @@ -65,7 +65,9 @@ "name": "nsxTPvdc1", "storageProfile": "Must-already-exist-storage-profile-name", "storageProfile2": "Must-already-exist-storage-profile-name2", - "networkPool": "NSX-T Overlay 1" + "networkPool": "NSX-T Overlay 1", + "//": "A VM Group that needs to exist in the backing vSphere. This VM Group can be used to create VM Placement Policies.", + "placementPolicyVmGroup": "testVmGroup" } }, "networking": { diff --git a/vcd/structure.go b/vcd/structure.go index ca022f0f3..f20b40165 100644 --- a/vcd/structure.go +++ b/vcd/structure.go @@ -96,6 +96,16 @@ func takeInt64Pointer(x int64) *int64 { return &x } +// getStringAttributeAsPointer returns a pointer to the value of the given attribute from the current resource data. +// If the attribute is empty, returns a nil pointer. +func getStringAttributeAsPointer(d *schema.ResourceData, attrName string) *string { + attributeValue := d.Get(attrName).(string) + if attributeValue == "" { + return nil + } + return &attributeValue +} + // extractUuid finds an UUID in the input string // Returns an empty string if no UUID was found func extractUuid(input string) string { @@ -145,6 +155,28 @@ func extractNamesFromOpenApiReferences(refs []types.OpenApiReference) []string { return resultStrings } +// extractIdsFromReferences extracts []string with IDs from []*types.Reference which contains ID and Names +func extractIdsFromReferences(refs []*types.Reference) []string { + resultStrings := make([]string, len(refs)) + for index := range refs { + resultStrings[index] = refs[index].ID + } + + return resultStrings +} + +// extractIdsFromVimObjectRefs extracts []string with IDs from []*types.VimObjectRef which contains *types.Reference +func extractIdsFromVimObjectRefs(refs []*types.VimObjectRef) []string { + var resultStrings []string + for index := range refs { + if refs[index].VimServerRef != nil { + resultStrings = append(resultStrings, refs[index].VimServerRef.ID) + } + } + + return resultStrings +} + // convertSliceOfStringsToOpenApiReferenceIds converts []string to []types.OpenApiReference by filling // types.OpenApiReference.ID fields func convertSliceOfStringsToOpenApiReferenceIds(ids []string) []types.OpenApiReference { @@ -156,6 +188,18 @@ func convertSliceOfStringsToOpenApiReferenceIds(ids []string) []types.OpenApiRef return resultReferences } +// contains returns true if `sliceToSearch` contains `searched`. Returns false otherwise. +func contains(sliceToSearch []string, searched string) bool { + found := false + for _, idInSlice := range sliceToSearch { + if searched == idInSlice { + found = true + break + } + } + return found +} + // MetadataCompatible allows to consider all structs that implement metadata handling to be the same type type metadataCompatible interface { GetMetadata() (*types.Metadata, error) diff --git a/vcd/testcheck_funcs_test.go b/vcd/testcheck_funcs_test.go index afb7f5938..3c8ab4fa0 100644 --- a/vcd/testcheck_funcs_test.go +++ b/vcd/testcheck_funcs_test.go @@ -1,5 +1,5 @@ -//go:build vapp || vm || user || nsxt || extnetwork || network || gateway || catalog || standaloneVm || alb || vdcGroup || ldap || ALL || functional -// +build vapp vm user nsxt extnetwork network gateway catalog standaloneVm alb vdcGroup ldap ALL functional +//go:build vapp || vm || user || nsxt || extnetwork || network || gateway || catalog || standaloneVm || alb || vdcGroup || ldap || vdc || ALL || functional +// +build vapp vm user nsxt extnetwork network gateway catalog standaloneVm alb vdcGroup ldap vdc ALL functional package vcd diff --git a/website/docs/d/org_vdc.html.markdown b/website/docs/d/org_vdc.html.markdown index 51b805ddf..01010f30a 100644 --- a/website/docs/d/org_vdc.html.markdown +++ b/website/docs/d/org_vdc.html.markdown @@ -8,7 +8,7 @@ description: |- # vcd\_org\_vdc -Provides a VMware Cloud Director Organization VDC data source. An Organization VDC can be used to reference a VCD and use its +Provides a VMware Cloud Director Organization VDC data source. An Organization VDC can be used to reference a VDC and use its data within other resources or data sources. -> **Note:** This resource supports NSX-T and NSX-V based Org VDCs diff --git a/website/docs/d/provider_vdc.html.markdown b/website/docs/d/provider_vdc.html.markdown new file mode 100644 index 000000000..95ae1bcbd --- /dev/null +++ b/website/docs/d/provider_vdc.html.markdown @@ -0,0 +1,75 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_provider_vdc" +sidebar_current: "docs-vcd-data-source-provider-vdc" +description: |- + Provides a Provider VDC data source. +--- + +# vcd\_provider\_vdc + +Provides a VMware Cloud Director Provider VDC data source. A Provider VDC can be used to reference a Provider VDC and use its +data within other resources or data sources. + +Supported in provider *v3.8+* + +## Example Usage + +```hcl +data "vcd_provider_vdc" "my-pvdc" { + name = "my-pvdc" +} + +output "provider_vdc" { + value = data.vcd_provider_vdc.my-pvdc.id +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Provider VDC name + +## Attribute reference + +* `description` - Optional description of the Provider VDC. +* `status` - Status of the Provider VDC, it can be -1 (creation failed), 0 (not ready), 1 (ready), 2 (unknown) or 3 (unrecognized). +* `is_enabled` - True if this Provider VDC is enabled and can provide resources to organization VDCs. A Provider VDC is always enabled on creation. +* `capabilities` - Set of virtual hardware versions supported by this Provider VDC. +* `compute_capacity` - An indicator of CPU and memory capacity. See [Compute Capacity](#compute-capacity) below for details. +* `compute_provider_scope` - Represents the compute fault domain for this Provider VDC. This value is a tenant-facing tag that is shown to tenants when viewing fault domains of the child Organization VDCs (for example, a VDC Group). +* `highest_supported_hardware_version` - The highest virtual hardware version supported by this Provider VDC. +* `nsxt_manager_id` - ID of the registered NSX-T Manager that backs networking operations for this Provider VDC. +* `storage_containers_ids` - Set of IDs of the vSphere Datastores backing this Provider VDC. +* `external_network_ids` - Set of IDs of External Networks. +* `storage_profile_ids` - Set of IDs to the Storage Profiles available to this Provider VDC. +* `resource_pool_ids` - Set of IDs of the Resource Pools backing this provider VDC. +* `network_pool_ids` - Set IDs of the Network Pools used by this Provider VDC. +* `universal_network_pool_id` - ID of the universal network reference. +* `host_ids` - Set with all the hosts which are connected to VC server. +* `vcenter_id` - ID of the vCenter Server that provides the Resource Pools and Datastores. +* `metadata` - Key and value pairs for Provider VDC Metadata. + + +## Compute Capacity + +The `compute_capacity` attribute is a list with a single item which has the following nested attributes: + +* `cpu` - An indicator of CPU. See [CPU and memory](#cpu-and-memory) below. +* `memory` - An indicator of memory. See [CPU and memory](#cpu-and-memory) below. +* `is_elastic` - True if compute capacity can grow or shrink based on demand. +* `is_ha` - True if compute capacity is highly available. + + +### CPU and memory + +The `cpu` and `memory` indicators have the following nested attributes: + +* `allocation` - Allocated CPU/Memory for this Provider VDC. +* `overhead` - CPU/Memory overhead for this Provider VDC. +* `reserved` - Reserved CPU/Memory for this Provider VDC. +* `total` - Total CPU/Memory for this Provider VDC. +* `units` - Units for the CPU/Memory of this Provider VDC. +* `used` - Used CPU/Memory in this Provider VDC. diff --git a/website/docs/d/vm_group.html.markdown b/website/docs/d/vm_group.html.markdown new file mode 100644 index 000000000..ff236438d --- /dev/null +++ b/website/docs/d/vm_group.html.markdown @@ -0,0 +1,40 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_vm_group" +sidebar_current: "docs-vcd-data-source-vm-group" +description: |- + Provides a VMware Cloud Director VM Group data source. This can be used to fetch vSphere VM Groups and create VM Placement Policies with them. +--- + +# vcd\_vm\_group + +Provides a VMware Cloud Director VM Group data source. This can be used to fetch vSphere VM Groups and create VM Placement Policies with them. + +Supported in provider *v3.8+* + +## Example Usage + +```hcl +data "vcd_provider_vdc" "my-vdc" { + name = "my-pvdc" +} + +data "vcd_vm_group" "vm-group" { + name = "vmware-license-group" + provider_vdc_id = data.vcd_provider_vdc.my-vdc.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of VM Group to fetch from vSphere. +* `provider_vdc_id` - (Required) The ID of [Provider VDC](/providers/vmware/vcd/latest/docs/data-sources/vcd_provider_vdc) to which the VM Group belongs. + +## Attributes reference + +* `cluster_name` - Name of the vSphere cluster associated to this VM Group. +* `named_vm_group_id` - ID of the named VM Group. Used to create Logical VM Groups. +* `vcenter_id` - ID of the vCenter server. +* `cluster_moref` - Managed object reference of the vSphere cluster associated to this VM Group. diff --git a/website/docs/d/vm_placement_policy.html.markdown b/website/docs/d/vm_placement_policy.html.markdown new file mode 100644 index 000000000..39fb265ba --- /dev/null +++ b/website/docs/d/vm_placement_policy.html.markdown @@ -0,0 +1,42 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_vm_placement_policy" +sidebar_current: "docs-vcd-data-source-vm-placement-policy" +description: |- + Provides a VMware Cloud Director VM Placement Policy data source. This can be + used to read a VM placement policy. +--- + +# vcd\_vm\_placement\_policy + +Provides a VMware Cloud Director VM Placement Policy data source. This can be +used to read a VM Placement Policy. + +Supported in provider *v3.8+* and requires VCD 10.2+ + +## Example Usage + +```hcl +data "vcd_provider_vdc" "my-pvdc" { + name = "my-pVDC" +} + +data "vcd_vm_placement_policy" "tf-policy-name" { + name = "my-policy" + provider_vdc_id = data.vcd_provider_vdc.my-pvdc.id +} + +output "policyId" { + value = data.vcd_vm_placement_policy.tf-policy-name.id +} +``` +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name VM Placement Policy. +* `provider_vdc_id` - (Required) The ID of the [Provider VDC](/providers/vmware/vcd/latest/docs/data-sources/vcd_provider_vdc) to which the VM Placement Policy belongs. + +## Attribute Reference + +All attributes defined in [`vcd_vm_placement_policy`](/providers/vmware/vcd/latest/docs/resources/vcd_vm_placement_policy#attribute-reference) resource are supported. \ No newline at end of file diff --git a/website/docs/guides/container_service_extension_3_1_x.html.markdown b/website/docs/guides/container_service_extension_3_1_x.html.markdown index 5ea5d7624..468236823 100644 --- a/website/docs/guides/container_service_extension_3_1_x.html.markdown +++ b/website/docs/guides/container_service_extension_3_1_x.html.markdown @@ -3,7 +3,7 @@ layout: "vcd" page_title: "VMware Cloud Director: Container Service Extension v3.1.x" sidebar_current: "docs-vcd-guides-cse" description: |- -Provides guidance on configuring VCD to be able to install Container Service Extension v3.1.x. + Provides guidance on configuring VCD to be able to install Container Service Extension v3.1.x. --- # Container Service Extension v3.1.x diff --git a/website/docs/r/org_vdc.html.markdown b/website/docs/r/org_vdc.html.markdown index 214cc70bd..3547b0a40 100644 --- a/website/docs/r/org_vdc.html.markdown +++ b/website/docs/r/org_vdc.html.markdown @@ -11,7 +11,7 @@ description: |- Provides a VMware Cloud Director Organization VDC resource. This can be used to create and delete an Organization VDC. Requires system administrator privileges. --> **Note:** This resource supports NSX-T and NSX-V based Org Vdcs by providing relevant +-> **Note:** This resource supports NSX-T and NSX-V based Org VDCs by providing relevant `network_pool_name` and `provider_vdc_name` Supported in provider *v2.2+* @@ -102,7 +102,7 @@ resource "vcd_org_vdc" "nsxt-vdc" { } ``` -## Example Usage (With VM sizing policies) +## Example Usage (With VM Sizing Policies) ```hcl resource "vcd_vm_sizing_policy" "size_1" { @@ -144,8 +144,42 @@ resource "vcd_org_vdc" "my-vdc" { description = "The pride of my work" org = "my-org" # ... - default_vm_sizing_policy_id = vcd_vm_sizing_policy.size_1.id - vm_sizing_policy_ids = [vcd_vm_sizing_policy.size_1.id, vcd_vm_sizing_policy.size_2.id] + default_compute_policy_id = vcd_vm_sizing_policy.size_1.id + vm_sizing_policy_ids = [vcd_vm_sizing_policy.size_1.id, vcd_vm_sizing_policy.size_2.id] +} +``` + +## Example Usage (With VM Placement Policies) + +```hcl +data "vcd_provider_vdc" "pvdc" { + name = "my-pvdc" +} + +# This VM group needs to exist in the backing vSphere +data "vcd_vm_group" "vmgroup" { + name = "vmware-licensed-vms" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id +} + +resource "vcd_vm_placement_policy" "new-placement-policy" { + name = "place-in-vmware-licensed" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + vm_group_ids = [data.vcd_vm_group.vmgroup.id] +} + +data "vcd_vm_placement_policy" "existing-policy" { + name = "place-in-company-licensed" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id +} + +resource "vcd_org_vdc" "my-vdc" { + name = "my-vdc" + description = "The pride of my work" + org = "my-org" + # ... + default_compute_policy_id = data.vcd_vm_placement_policy.existing-policy.id + vm_placement_policy_ids = [data.vcd_vm_placement_policy.existing-policy.id, vcd_vm_placement_policy.new-placement-policy.id] } ``` @@ -163,15 +197,15 @@ The following arguments are supported: * AllocationVApp ("Pay as you go") * AllocationPool ("Allocation pool") * ReservationPool ("Reservation pool") - * Flex ("Flex") (*v2.7+*, *vCD 9.7+*) + * Flex ("Flex") (*v2.7+*, *VCD 9.7+*) * `compute_capacity` - (Required) The compute capacity allocated to this VDC. See [Compute Capacity](#computecapacity) below for details. * `nic_quota` - (Optional) Maximum number of virtual NICs allowed in this VDC. Defaults to 0, which specifies an unlimited number. * `network_quota` - (Optional) Maximum number of network objects that can be deployed in this VDC. Defaults to 0, which means no networks can be deployed. * `vm_quota` - (Optional) The maximum number of VMs that can be created in this VDC. Includes deployed and undeployed VMs in vApps and vApp templates. Defaults to 0, which specifies an unlimited number. * `enabled` - (Optional) True if this VDC is enabled for use by the organization VDCs. Default is true. * `storage_profile` - (Required, System Admin) Storage profiles supported by this VDC. See [Storage Profile](#storageprofile) below for details. -* `memory_guaranteed` - (Optional, System Admin) Percentage of allocated memory resources guaranteed to vApps deployed in this VDC. For example, if this value is 0.75, then 75% of allocated resources are guaranteed. Required when `allocation_model` is AllocationVApp, AllocationPool or Flex. When Allocation model is AllocationPool minimum value is 0.2. If left empty, vCD sets a value. -* `cpu_guaranteed` - (Optional, System Admin) Percentage of allocated CPU resources guaranteed to vApps deployed in this VDC. For example, if this value is 0.75, then 75% of allocated resources are guaranteed. Required when `allocation_model` is AllocationVApp, AllocationPool or Flex. If left empty, vCD sets a value. +* `memory_guaranteed` - (Optional, System Admin) Percentage of allocated memory resources guaranteed to vApps deployed in this VDC. For example, if this value is 0.75, then 75% of allocated resources are guaranteed. Required when `allocation_model` is AllocationVApp, AllocationPool or Flex. When Allocation model is AllocationPool minimum value is 0.2. If left empty, VCD sets a value. +* `cpu_guaranteed` - (Optional, System Admin) Percentage of allocated CPU resources guaranteed to vApps deployed in this VDC. For example, if this value is 0.75, then 75% of allocated resources are guaranteed. Required when `allocation_model` is AllocationVApp, AllocationPool or Flex. If left empty, VCD sets a value. * `cpu_speed` - (Optional, System Admin) Specifies the clock frequency, in Megahertz, for any virtual CPU that is allocated to a VM. A VM with 2 vCPUs will consume twice as much of this value. Ignored for ReservationPool. Required when `allocation_model` is AllocationVApp, AllocationPool or Flex, and may not be less than 256 MHz. Defaults to 1000 MHz if value isn't provided. * `metadata` - (Optional; *v2.4+*) Key value map of metadata to assign to this VDC * `enable_thin_provisioning` - (Optional, System Admin) Boolean to request thin provisioning. Request will be honored only if the underlying data store supports it. Thin provisioning saves storage space by committing it on demand. This allows over-allocation of storage. @@ -179,12 +213,14 @@ The following arguments are supported: * `network_pool_name` - (Optional, System Admin) Reference to a network pool in the Provider VDC. Required if this VDC will contain routed or isolated networks. * `allow_over_commit` - (Optional) Set to false to disallow creation of the VDC if the `allocation_model` is AllocationPool or ReservationPool and the ComputeCapacity you specified is greater than what the backing Provider VDC can supply. Default is true. * `enable_vm_discovery` - (Optional) If true, discovery of vCenter VMs is enabled for resource pools backing this VDC. If false, discovery is disabled. If left unspecified, the actual behaviour depends on enablement at the organization level and at the system level. -* `elasticity` - (Optional, *v2.7+*, *vCD 9.7+*) Indicates if the Flex VDC should be elastic. Required with the Flex allocation model. -* `include_vm_memory_overhead` - (Optional, *v2.7+*, *vCD 9.7+*) Indicates if the Flex VDC should include memory overhead into its accounting for admission control. Required with the Flex allocation model. +* `elasticity` - (Optional, *v2.7+*, *VCD 9.7+*) Indicates if the Flex VDC should be elastic. Required with the Flex allocation model. +* `include_vm_memory_overhead` - (Optional, *v2.7+*, *VCD 9.7+*) Indicates if the Flex VDC should include memory overhead into its accounting for admission control. Required with the Flex allocation model. * `delete_force` - (Required) When destroying use `delete_force=True` to remove a VDC and any objects it contains, regardless of their state. * `delete_recursive` - (Required) When destroying use `delete_recursive=True` to remove the VDC and any objects it contains that are in a state that normally allows removal. -* `default_vm_sizing_policy_id` - (Optional, *v3.0+*, *vCD 10.0+*) Set of VM sizing policy IDs. This field requires `vm_sizing_policy_ids` to be configured together. -* `vm_sizing_policy_ids` - (Optional, *v3.0+*, *vCD 10.0+*) Default VM sizing policy ID. This field requires `default_vm_sizing_policy_id` to be configured together. +* `default_compute_policy_id` - (Optional, *v3.8+*, *VCD 10.2+*) ID of the default Compute Policy for this VDC. It can be a VM Sizing Policy, a VM Placement Policy or a vGPU Policy. +* `default_vm_sizing_policy_id` - (Deprecated; Optional, *v3.0+*, *VCD 10.2+*) ID of the default Compute Policy for this VDC. It can be a VM Sizing Policy, a VM Placement Policy or a vGPU Policy. Deprecated in favor of `default_compute_policy_id`. +* `vm_sizing_policy_ids` - (Optional, *v3.0+*, *VCD 10.2+*) Set of IDs of VM Sizing policies that are assigned to this VDC. This field requires `default_compute_policy_id` to be configured together. +* `vm_placement_policy_ids` - (Optional, *v3.8+*, *VCD 10.2+*) Set of IDs of VM Placement policies that are assigned to this VDC. This field requires `default_compute_policy_id` to be configured together. ## Storage Profile diff --git a/website/docs/r/vm_placement_policy.html.markdown b/website/docs/r/vm_placement_policy.html.markdown new file mode 100644 index 000000000..5e52c9413 --- /dev/null +++ b/website/docs/r/vm_placement_policy.html.markdown @@ -0,0 +1,92 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_vm_placement_policy" +sidebar_current: "docs-vcd-resource-vm-placement-policy" +description: |- + Provides a VMware Cloud Director VM Placement Policy resource. This can be + used to create, modify, and delete VM Placement Policies. +--- + +# vcd\_vm\_placement\_policy + +Provides a VMware Cloud Director VM Placement Policy resource. This can be +used to create, modify, and delete VM Placement Policy. + +Supported in provider *v3.8+* and requires VCD 10.2+ + +-> **Note:** This resource requires system administrator privileges. + +## Example Usage + +```hcl +data "vcd_provider_vdc" "pvdc" { + name = "my-pvdc" +} + +data "vcd_vm_group" "vm-group" { + name = "vmware-vm-group" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id +} + +resource "vcd_vm_placement_policy" "test-placement-pol" { + name = "my-placement-pol" + description = "My awesome VM Placement Policy" + provider_vdc_id = data.vcd_provider_vdc.pvdc.id + vm_group_ids = [data.vcd_vm_group.vm-group.id] +} +``` +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of VM Placement Policy. +* `provider_vdc_id` - (Required) The ID of the Provider VDC to which this VM Placement Policy belongs. +* `description` - (Optional) description of VM Placement Policy. +* `vm_group_ids` - (Optional) IDs of the collection of VMs with similar host requirements. **Note:** Either `vm_group_ids` or `logical_vm_group_ids` must be set. +* `logical_vm_group_ids` - (Optional) IDs of one or more Logical VM Groups to define this VM Placement policy. There is an AND relationship among all the entries set in this attribute. **Note:** Either `vm_group_ids` or `logical_vm_group_ids` must be set. + +# Importing + +~> **Note:** The current implementation of Terraform import can only import resources into the state. +It does not generate configuration. [More information.](https://www.terraform.io/docs/import/) + +An existing an VM Placement Policy can be [imported][docs-import] into this resource +via supplying the full dot separated path to VM Placement Policy. An example is +below: + +``` +terraform import vcd_vm_placement_policy.my-policy policy_name_in_vcd +``` +or using IDs: +``` +terraform import vcd_vm_placement_policy.my-policy urn:vcloud:vdcComputePolicy:39579936-6211-40b5-adf6-2a74d4413e9e +``` + +NOTE: the default separator (.) can be changed using Provider.import_separator or variable VCD_IMPORT_SEPARATOR + +[docs-import]:https://www.terraform.io/docs/import/ + +After that, you can expand the configuration file and either update or delete the VM Placement Policy as needed. Running `terraform plan` +at this stage will show the difference between the minimal configuration file and the VM Placement Policy stored properties. + +### Listing VM Placement Policies + +If you want to list IDs there is a special command **`terraform import vcd_vm_placement_policy.imported list@`**. +The output for this command should look similar to the one below: + +``` +terraform import vcd_vm_placement_policy.imported list@ +vcd_vm_placement_policy.import: Importing from ID "list@"... +Retrieving all VM Placement Policies +No ID Name +-- -- ---- +1 urn:vcloud:vdcComputePolicy:100dc35a-572b-4876-a762-c734d67c56ef tf_policy_3 +2 urn:vcloud:vdcComputePolicy:446d623e-1eec-4c8c-8a14-2f7e6086546b tf_policy_2 + +``` + +Now to import VM Placement Policy with ID urn:vcloud:vdcComputePolicy:446d623e-1eec-4c8c-8a14-2f7e6086546b one could supply this command: + +```shell +$ terraform import vcd_vm_placement_policy.imported urn:vcloud:vdcComputePolicy:446d623e-1eec-4c8c-8a14-2f7e6086546b +``` diff --git a/website/vcd.erb b/website/vcd.erb index 66a4155af..8b273c9dc 100644 --- a/website/vcd.erb +++ b/website/vcd.erb @@ -109,6 +109,12 @@ > vcd_vm_sizing_policy + > + vcd_vm_group + + > + vcd_vm_placement_policy + > vcd_independent_disk @@ -145,6 +151,9 @@ > vcd_vcenter + > + vcd_provider_vdc + > vcd_portgroup @@ -348,6 +357,9 @@ > vcd_vm_sizing_policy + > + vm_placement_policy + > vcd_vm_internal_disk