diff --git a/vsphere/helper_test.go b/vsphere/helper_test.go index 47390eb97..88a38aac4 100644 --- a/vsphere/helper_test.go +++ b/vsphere/helper_test.go @@ -749,6 +749,16 @@ func testGetComputeCluster(s *terraform.State, resourceName string) (*object.Clu return clustercomputeresource.FromID(vars.client, vars.resourceID) } +// testGetComputeClusterFromDataSource is a convenience method to fetch a +// compute cluster via the data in a vsphere_compute_cluster data source. +func testGetComputeClusterFromDataSource(s *terraform.State, resourceName string) (*object.ClusterComputeResource, error) { + vars, err := testClientVariablesForResource(s, fmt.Sprintf("data.%s.%s", resourceVSphereComputeClusterName, resourceName)) + if err != nil { + return nil, err + } + return clustercomputeresource.FromID(vars.client, vars.resourceID) +} + // testGetComputeClusterProperties is a convenience method that adds an extra // step to testGetComputeCluster to get the properties of a // ClusterComputeResource. @@ -759,3 +769,33 @@ func testGetComputeClusterProperties(s *terraform.State, resourceName string) (* } return clustercomputeresource.Properties(cluster) } + +// testGetComputeClusterDRSVMConfig is a convenience method to fetch a VM's DRS +// override in a (compute) cluster. +func testGetComputeClusterDRSVMConfig(s *terraform.State, resourceName string) (*types.ClusterDrsVmConfigInfo, error) { + vars, err := testClientVariablesForResource(s, fmt.Sprintf("%s.%s", resourceVSphereDRSVMOverrideName, resourceName)) + if err != nil { + return nil, err + } + + if vars.resourceID == "" { + return nil, errors.New("resource ID is empty") + } + + clusterID, vmID, err := resourceVSphereDRSVMOverrideParseID(vars.resourceID) + if err != nil { + return nil, err + } + + cluster, err := clustercomputeresource.FromID(vars.client, clusterID) + if err != nil { + return nil, err + } + + vm, err := virtualmachine.FromUUID(vars.client, vmID) + if err != nil { + return nil, err + } + + return resourceVSphereDRSVMOverrideFindEntry(cluster, vm) +} diff --git a/vsphere/internal/helper/virtualmachine/virtual_machine_helper.go b/vsphere/internal/helper/virtualmachine/virtual_machine_helper.go index c4b335aea..6ca1777c6 100644 --- a/vsphere/internal/helper/virtualmachine/virtual_machine_helper.go +++ b/vsphere/internal/helper/virtualmachine/virtual_machine_helper.go @@ -50,6 +50,12 @@ func newUUIDNotFoundError(s string) *UUIDNotFoundError { } } +// IsUUIDNotFoundError returns true if the error is a UUIDNotFoundError. +func IsUUIDNotFoundError(err error) bool { + _, ok := err.(*UUIDNotFoundError) + return ok +} + // FromUUID locates a virtualMachine by its UUID. func FromUUID(client *govmomi.Client, uuid string) (*object.VirtualMachine, error) { log.Printf("[DEBUG] Locating virtual machine with UUID %q", uuid) diff --git a/vsphere/provider.go b/vsphere/provider.go index 0d63da8c2..4467c5788 100644 --- a/vsphere/provider.go +++ b/vsphere/provider.go @@ -94,6 +94,7 @@ func Provider() terraform.ResourceProvider { "vsphere_datastore_cluster": resourceVSphereDatastoreCluster(), "vsphere_distributed_port_group": resourceVSphereDistributedPortGroup(), "vsphere_distributed_virtual_switch": resourceVSphereDistributedVirtualSwitch(), + "vsphere_drs_vm_override": resourceVSphereDRSVMOverride(), "vsphere_file": resourceVSphereFile(), "vsphere_folder": resourceVSphereFolder(), "vsphere_host_port_group": resourceVSphereHostPortGroup(), diff --git a/vsphere/resource_vsphere_drs_vm_override.go b/vsphere/resource_vsphere_drs_vm_override.go new file mode 100644 index 000000000..83749653e --- /dev/null +++ b/vsphere/resource_vsphere_drs_vm_override.go @@ -0,0 +1,382 @@ +package vsphere + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/clustercomputeresource" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/structure" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/viapi" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/virtualmachine" + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/types" +) + +const resourceVSphereDRSVMOverrideName = "vsphere_drs_vm_override" + +func resourceVSphereDRSVMOverride() *schema.Resource { + return &schema.Resource{ + Create: resourceVSphereDRSVMOverrideCreate, + Read: resourceVSphereDRSVMOverrideRead, + Update: resourceVSphereDRSVMOverrideUpdate, + Delete: resourceVSphereDRSVMOverrideDelete, + Importer: &schema.ResourceImporter{ + State: resourceVSphereDRSVMOverrideImport, + }, + + Schema: map[string]*schema.Schema{ + "compute_cluster_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The managed object ID of the cluster.", + }, + "virtual_machine_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The managed object ID of the virtual machine.", + }, + "drs_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Enable DRS for this virtual machine.", + }, + "drs_automation_level": { + Type: schema.TypeString, + Optional: true, + Default: string(types.DrsBehaviorManual), + Description: "The automation level for this virtual machine in the cluster. Can be one of manual, partiallyAutomated, or fullyAutomated.", + ValidateFunc: validation.StringInSlice(drsBehaviorAllowedValues, false), + }, + }, + } +} + +func resourceVSphereDRSVMOverrideCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] %s: Beginning create", resourceVSphereDRSVMOverrideIDString(d)) + + cluster, vm, err := resourceVSphereDRSVMOverrideObjects(d, meta) + if err != nil { + return err + } + + info, err := expandClusterDrsVMConfigInfo(d, vm) + if err != nil { + return err + } + spec := &types.ClusterConfigSpecEx{ + DrsVmConfigSpec: []types.ClusterDrsVmConfigSpec{ + { + ArrayUpdateSpec: types.ArrayUpdateSpec{ + Operation: types.ArrayUpdateOperationAdd, + }, + Info: info, + }, + }, + } + + if err = clustercomputeresource.Reconfigure(cluster, spec); err != nil { + return err + } + + id, err := resourceVSphereDRSVMOverrideFlattenID(cluster, vm) + if err != nil { + return fmt.Errorf("cannot compute ID of created resource: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] %s: Create finished successfully", resourceVSphereDRSVMOverrideIDString(d)) + return resourceVSphereDRSVMOverrideRead(d, meta) +} + +func resourceVSphereDRSVMOverrideRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] %s: Beginning read", resourceVSphereDRSVMOverrideIDString(d)) + + cluster, vm, err := resourceVSphereDRSVMOverrideObjects(d, meta) + if err != nil { + return err + } + + info, err := resourceVSphereDRSVMOverrideFindEntry(cluster, vm) + if err != nil { + return err + } + + if info == nil { + // The configuration is missing, blank out the ID so it can be re-created. + d.SetId("") + return nil + } + + // Save the compute_cluster_id and virtual_machine_id here. These are + // ForceNew, but we set these for completeness on import so that if the wrong + // cluster/VM combo was used, it will be noted. + if err = d.Set("compute_cluster_id", cluster.Reference().Value); err != nil { + return fmt.Errorf("error setting attribute \"compute_cluster_id\": %s", err) + } + + props, err := virtualmachine.Properties(vm) + if err != nil { + return fmt.Errorf("error getting properties of virtual machine: %s", err) + } + if err = d.Set("virtual_machine_id", props.Config.Uuid); err != nil { + return fmt.Errorf("error setting attribute \"virtual_machine_id\": %s", err) + } + + if err = flattenClusterDrsVMConfigInfo(d, info); err != nil { + return err + } + + log.Printf("[DEBUG] %s: Read completed successfully", resourceVSphereDRSVMOverrideIDString(d)) + return nil +} + +func resourceVSphereDRSVMOverrideUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] %s: Beginning update", resourceVSphereDRSVMOverrideIDString(d)) + + cluster, vm, err := resourceVSphereDRSVMOverrideObjects(d, meta) + if err != nil { + return err + } + + info, err := expandClusterDrsVMConfigInfo(d, vm) + if err != nil { + return err + } + spec := &types.ClusterConfigSpecEx{ + DrsVmConfigSpec: []types.ClusterDrsVmConfigSpec{ + { + ArrayUpdateSpec: types.ArrayUpdateSpec{ + // NOTE: ArrayUpdateOperationAdd here replaces existing entries, + // versus adding duplicates or "merging" old settings with new ones + // that have missing fields. + Operation: types.ArrayUpdateOperationAdd, + }, + Info: info, + }, + }, + } + + if err := clustercomputeresource.Reconfigure(cluster, spec); err != nil { + return err + } + + log.Printf("[DEBUG] %s: Update finished successfully", resourceVSphereDRSVMOverrideIDString(d)) + return resourceVSphereDRSVMOverrideRead(d, meta) +} + +func resourceVSphereDRSVMOverrideDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] %s: Beginning delete", resourceVSphereDRSVMOverrideIDString(d)) + + cluster, vm, err := resourceVSphereDRSVMOverrideObjects(d, meta) + if err != nil { + return err + } + + spec := &types.ClusterConfigSpecEx{ + DrsVmConfigSpec: []types.ClusterDrsVmConfigSpec{ + { + ArrayUpdateSpec: types.ArrayUpdateSpec{ + Operation: types.ArrayUpdateOperationRemove, + RemoveKey: vm.Reference(), + }, + }, + }, + } + + if err := clustercomputeresource.Reconfigure(cluster, spec); err != nil { + return err + } + + log.Printf("[DEBUG] %s: Deleted successfully", resourceVSphereDRSVMOverrideIDString(d)) + return nil +} + +func resourceVSphereDRSVMOverrideImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + var data map[string]string + if err := json.Unmarshal([]byte(d.Id()), &data); err != nil { + return nil, err + } + clusterPath, ok := data["compute_cluster_path"] + if !ok { + return nil, errors.New("missing compute_cluster_path in input data") + } + vmPath, ok := data["virtual_machine_path"] + if !ok { + return nil, errors.New("missing virtual_machine_path in input data") + } + + client, err := resourceVSphereDRSVMOverrideClient(meta) + if err != nil { + return nil, err + } + + cluster, err := clustercomputeresource.FromPath(client, clusterPath, nil) + if err != nil { + return nil, fmt.Errorf("cannot locate cluster %q: %s", clusterPath, err) + } + + vm, err := virtualmachine.FromPath(client, vmPath, nil) + if err != nil { + return nil, fmt.Errorf("cannot locate virtual machine %q: %s", vmPath, err) + } + + id, err := resourceVSphereDRSVMOverrideFlattenID(cluster, vm) + if err != nil { + return nil, fmt.Errorf("cannot compute ID of imported resource: %s", err) + } + d.SetId(id) + return []*schema.ResourceData{d}, nil +} + +// expandClusterDrsVMConfigInfo reads certain ResourceData keys and returns a +// ClusterDrsVmConfigInfo. +func expandClusterDrsVMConfigInfo(d *schema.ResourceData, vm *object.VirtualMachine) (*types.ClusterDrsVmConfigInfo, error) { + obj := &types.ClusterDrsVmConfigInfo{ + Behavior: types.DrsBehavior(d.Get("drs_automation_level").(string)), + Enabled: structure.GetBool(d, "drs_enabled"), + Key: vm.Reference(), + } + + return obj, nil +} + +// flattenClusterDrsVmConfigInfo saves a ClusterDrsVmConfigInfo into the +// supplied ResourceData. +func flattenClusterDrsVMConfigInfo(d *schema.ResourceData, obj *types.ClusterDrsVmConfigInfo) error { + return structure.SetBatch(d, map[string]interface{}{ + "drs_automation_level": obj.Behavior, + "drs_enabled": obj.Enabled, + }) +} + +// resourceVSphereDRSVMOverrideIDString prints a friendly string for the +// vsphere_storage_drs_vm_config resource. +func resourceVSphereDRSVMOverrideIDString(d structure.ResourceIDStringer) string { + return structure.ResourceIDString(d, resourceVSphereDRSVMOverrideName) +} + +// resourceVSphereDRSVMOverrideFlattenID makes an ID for the +// vsphere_storage_drs_vm_config resource. +func resourceVSphereDRSVMOverrideFlattenID(cluster *object.ClusterComputeResource, vm *object.VirtualMachine) (string, error) { + clusterID := cluster.Reference().Value + props, err := virtualmachine.Properties(vm) + if err != nil { + return "", fmt.Errorf("cannot compute ID off of properties of virtual machine: %s", err) + } + vmID := props.Config.Uuid + return strings.Join([]string{clusterID, vmID}, ":"), nil +} + +// resourceVSphereDRSVMOverrideParseID parses an ID for the +// vsphere_storage_drs_vm_config and outputs its parts. +func resourceVSphereDRSVMOverrideParseID(id string) (string, string, error) { + parts := strings.SplitN(id, ":", 3) + if len(parts) < 2 { + return "", "", fmt.Errorf("bad ID %q", id) + } + return parts[0], parts[1], nil +} + +// resourceVSphereDRSVMOverrideFindEntry attempts to locate an existing DRS VM +// config in a cluster's configuration. It's used by the resource's read +// functionality and tests. nil is returned if the entry cannot be found. +func resourceVSphereDRSVMOverrideFindEntry( + cluster *object.ClusterComputeResource, + vm *object.VirtualMachine, +) (*types.ClusterDrsVmConfigInfo, error) { + props, err := clustercomputeresource.Properties(cluster) + if err != nil { + return nil, fmt.Errorf("error fetching cluster properties: %s", err) + } + + for _, info := range props.ConfigurationEx.(*types.ClusterConfigInfoEx).DrsVmConfig { + if info.Key == vm.Reference() { + log.Printf("[DEBUG] Found DRS config info for VM %q in cluster %q", vm.Name(), cluster.Name()) + return &info, nil + } + } + + log.Printf("[DEBUG] No DRS config info found for VM %q in cluster %q", vm.Name(), cluster.Name()) + return nil, nil +} + +// resourceVSphereDRSVMOverrideObjects handles the fetching of the cluster and +// virtual machine depending on what attributes are available: +// * If the resource ID is available, the data is derived from the ID. +// * If not, it's derived from the compute_cluster_id and virtual_machine_id +// attributes. +func resourceVSphereDRSVMOverrideObjects( + d *schema.ResourceData, + meta interface{}, +) (*object.ClusterComputeResource, *object.VirtualMachine, error) { + if d.Id() != "" { + return resourceVSphereDRSVMOverrideObjectsFromID(d, meta) + } + return resourceVSphereDRSVMOverrideObjectsFromAttributes(d, meta) +} + +func resourceVSphereDRSVMOverrideObjectsFromAttributes( + d *schema.ResourceData, + meta interface{}, +) (*object.ClusterComputeResource, *object.VirtualMachine, error) { + return resourceVSphereDRSVMOverrideFetchObjects( + meta, + d.Get("compute_cluster_id").(string), + d.Get("virtual_machine_id").(string), + ) +} + +func resourceVSphereDRSVMOverrideObjectsFromID( + d structure.ResourceIDStringer, + meta interface{}, +) (*object.ClusterComputeResource, *object.VirtualMachine, error) { + // Note that this function uses structure.ResourceIDStringer to satisfy + // interfacer. Adding exceptions in the comments does not seem to work. + // Change this back to ResourceData if it's needed in the future. + clusterID, vmID, err := resourceVSphereDRSVMOverrideParseID(d.Id()) + if err != nil { + return nil, nil, err + } + + return resourceVSphereDRSVMOverrideFetchObjects(meta, clusterID, vmID) +} + +func resourceVSphereDRSVMOverrideFetchObjects( + meta interface{}, + clusterID string, + vmID string, +) (*object.ClusterComputeResource, *object.VirtualMachine, error) { + client, err := resourceVSphereDRSVMOverrideClient(meta) + if err != nil { + return nil, nil, err + } + + cluster, err := clustercomputeresource.FromID(client, clusterID) + if err != nil { + return nil, nil, fmt.Errorf("cannot locate cluster: %s", err) + } + + vm, err := virtualmachine.FromUUID(client, vmID) + if err != nil { + return nil, nil, fmt.Errorf("cannot locate virtual machine: %s", err) + } + + return cluster, vm, nil +} + +func resourceVSphereDRSVMOverrideClient(meta interface{}) (*govmomi.Client, error) { + client := meta.(*VSphereClient).vimClient + if err := viapi.ValidateVirtualCenter(client); err != nil { + return nil, err + } + return client, nil +} diff --git a/vsphere/resource_vsphere_drs_vm_override_test.go b/vsphere/resource_vsphere_drs_vm_override_test.go new file mode 100644 index 000000000..b86234f34 --- /dev/null +++ b/vsphere/resource_vsphere_drs_vm_override_test.go @@ -0,0 +1,352 @@ +package vsphere + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/structure" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/viapi" + "github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/virtualmachine" + "github.com/vmware/govmomi/vim25/types" +) + +func TestAccResourceVSphereDRSVMOverride_drs(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereDRSVMOverridePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDRSVMOverrideExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDRSVMOverrideConfigOverrideDRSEnabled(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDRSVMOverrideExists(true), + testAccResourceVSphereDRSVMOverrideMatch(types.DrsBehaviorManual, false), + ), + }, + }, + }) +} + +func TestAccResourceVSphereDRSVMOverride_automationLevel(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereDRSVMOverridePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDRSVMOverrideExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDRSVMOverrideConfigOverrideAutomationLevel(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDRSVMOverrideExists(true), + testAccResourceVSphereDRSVMOverrideMatch(types.DrsBehaviorFullyAutomated, true), + ), + }, + }, + }) +} + +func TestAccResourceVSphereDRSVMOverride_update(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereDRSVMOverridePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDRSVMOverrideExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDRSVMOverrideConfigOverrideDRSEnabled(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDRSVMOverrideExists(true), + testAccResourceVSphereDRSVMOverrideMatch(types.DrsBehaviorManual, false), + ), + }, + { + Config: testAccResourceVSphereDRSVMOverrideConfigOverrideAutomationLevel(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDRSVMOverrideExists(true), + testAccResourceVSphereDRSVMOverrideMatch(types.DrsBehaviorFullyAutomated, true), + ), + }, + }, + }) +} + +func TestAccResourceVSphereDRSVMOverride_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereDRSVMOverridePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDRSVMOverrideExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDRSVMOverrideConfigOverrideDRSEnabled(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDRSVMOverrideExists(true), + testAccResourceVSphereDRSVMOverrideMatch(types.DrsBehaviorManual, false), + ), + }, + { + ResourceName: "vsphere_drs_vm_override.drs_vm_override", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + cluster, err := testGetComputeClusterFromDataSource(s, "cluster") + if err != nil { + return "", err + } + vm, err := testGetVirtualMachine(s, "vm") + if err != nil { + return "", err + } + + m := make(map[string]string) + m["compute_cluster_path"] = cluster.InventoryPath + m["virtual_machine_path"] = vm.InventoryPath + b, err := json.Marshal(m) + if err != nil { + return "", err + } + + return string(b), nil + }, + Config: testAccResourceVSphereDRSVMOverrideConfigOverrideDRSEnabled(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDRSVMOverrideExists(true), + testAccResourceVSphereDRSVMOverrideMatch(types.DrsBehaviorManual, false), + ), + }, + }, + }) +} + +func testAccResourceVSphereDRSVMOverridePreCheck(t *testing.T) { + if os.Getenv("VSPHERE_DATACENTER") == "" { + t.Skip("set VSPHERE_DATACENTER to run vsphere_storage_drs_vm_override acceptance tests") + } + if os.Getenv("VSPHERE_DATASTORE") == "" { + t.Skip("set VSPHERE_DATASTORE to run vsphere_storage_drs_vm_override acceptance tests") + } + if os.Getenv("VSPHERE_CLUSTER") == "" { + t.Skip("set VSPHERE_CLUSTER to run vsphere_storage_drs_vm_override acceptance tests") + } + if os.Getenv("VSPHERE_NETWORK_LABEL_PXE") == "" { + t.Skip("set VSPHERE_NETWORK_LABEL_PXE to run vsphere_storage_drs_vm_override acceptance tests") + } +} + +func testAccResourceVSphereDRSVMOverrideExists(expected bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + info, err := testGetComputeClusterDRSVMConfig(s, "drs_vm_override") + if err != nil { + if expected == false { + switch { + case viapi.IsManagedObjectNotFoundError(err): + fallthrough + case virtualmachine.IsUUIDNotFoundError(err): + // This is not necessarily a missing override, but more than likely a + // missing cluster, which happens during destroy as the dependent + // resources will be missing as well, so want to treat this as a + // deleted override as well. + return nil + } + } + return err + } + + switch { + case info == nil && !expected: + // Expected missing + return nil + case info == nil && expected: + // Expected to exist + return errors.New("DRS VM override missing when expected to exist") + case !expected: + return errors.New("DRS VM override still present when expected to be missing") + } + + return nil + } +} + +func testAccResourceVSphereDRSVMOverrideMatch(behavior types.DrsBehavior, enabled bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + actual, err := testGetComputeClusterDRSVMConfig(s, "drs_vm_override") + if err != nil { + return err + } + + if actual == nil { + return errors.New("DRS VM override missing") + } + + expected := &types.ClusterDrsVmConfigInfo{ + Behavior: behavior, + Enabled: structure.BoolPtr(enabled), + Key: actual.Key, + } + + if !reflect.DeepEqual(expected, actual) { + return spew.Errorf("expected %#v got %#v", expected, actual) + } + + return nil + } +} + +func testAccResourceVSphereDRSVMOverrideConfigOverrideDRSEnabled() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "datastore" { + default = "%s" +} + +variable "cluster" { + default = "%s" +} + +variable "network_label" { + default = "%s" +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +data "vsphere_datastore" "datastore" { + name = "${var.datastore}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +data "vsphere_compute_cluster" "cluster" { + name = "${var.cluster}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +data "vsphere_network" "network" { + name = "${var.network_label}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +resource "vsphere_virtual_machine" "vm" { + name = "terraform-test" + resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}" + datastore_id = "${data.vsphere_datastore.datastore.id}" + + num_cpus = 2 + memory = 2048 + guest_id = "other3xLinux64Guest" + + wait_for_guest_net_timeout = -1 + + network_interface { + network_id = "${data.vsphere_network.network.id}" + } + + disk { + label = "disk0" + size = 20 + } +} + +resource "vsphere_drs_vm_override" "drs_vm_override" { + compute_cluster_id = "${data.vsphere_compute_cluster.cluster.id}" + virtual_machine_id = "${vsphere_virtual_machine.vm.id}" + drs_enabled = false +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_DATASTORE"), + os.Getenv("VSPHERE_CLUSTER"), + os.Getenv("VSPHERE_NETWORK_LABEL_PXE"), + ) +} + +func testAccResourceVSphereDRSVMOverrideConfigOverrideAutomationLevel() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "datastore" { + default = "%s" +} + +variable "cluster" { + default = "%s" +} + +variable "network_label" { + default = "%s" +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +data "vsphere_datastore" "datastore" { + name = "${var.datastore}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +data "vsphere_compute_cluster" "cluster" { + name = "${var.cluster}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +data "vsphere_network" "network" { + name = "${var.network_label}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +resource "vsphere_virtual_machine" "vm" { + name = "terraform-test" + resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}" + datastore_id = "${data.vsphere_datastore.datastore.id}" + + num_cpus = 2 + memory = 2048 + guest_id = "other3xLinux64Guest" + + wait_for_guest_net_timeout = -1 + + network_interface { + network_id = "${data.vsphere_network.network.id}" + } + + disk { + label = "disk0" + size = 20 + } +} + +resource "vsphere_drs_vm_override" "drs_vm_override" { + compute_cluster_id = "${data.vsphere_compute_cluster.cluster.id}" + virtual_machine_id = "${vsphere_virtual_machine.vm.id}" + drs_enabled = true + drs_automation_level = "fullyAutomated" +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_DATASTORE"), + os.Getenv("VSPHERE_CLUSTER"), + os.Getenv("VSPHERE_NETWORK_LABEL_PXE"), + ) +} diff --git a/website/docs/r/drs_vm_override.html.markdown b/website/docs/r/drs_vm_override.html.markdown new file mode 100644 index 000000000..75e896629 --- /dev/null +++ b/website/docs/r/drs_vm_override.html.markdown @@ -0,0 +1,133 @@ +--- +layout: "vsphere" +page_title: "VMware vSphere: vsphere_drs_vm_override" +sidebar_current: "docs-vsphere-resource-storage-storage-drs-vm-override" +description: |- + Provides a VMware vSphere DRS virtual machine override resource. This can be used to override DRS settings in a cluster. +--- + +# vsphere\_drs\_vm\_override + +The `vsphere_drs_vm_override` resource can be used to add a DRS override to a +cluster for a specific virtual machine. With this resource, one can enable or +disable DRS and control the automation level for a single virtual machine +without affecting the rest of the cluster. + +For more information on vSphere clusters and DRS, see [this +page][ref-vsphere-drs-clusters]. + +[ref-vsphere-drs-clusters]: https://docs.vmware.com/en/VMware-vSphere/6.5/com.vmware.vsphere.resmgmt.doc/GUID-8ACF3502-5314-469F-8CC9-4A9BD5925BC2.html + +## Example Usage + +The example below creates a virtual machine in a cluster using the +[`vsphere_virtual_machine`][tf-vsphere-vm-resource] resource, creating the +virtual machine in the cluster looked up by the +[`vsphere_compute_cluster`][tf-vsphere-cluster-data-source] data source, but also +pinning the VM to a host defined by the +[`vsphere_host`][tf-vsphere-host-data-source] data source, which is assumed to +be a host within the cluster. To ensure that the VM stays on this host and does +not need to be migrated back at any point in time, an override is entered using +the `vsphere_drs_vm_override` resource that disables DRS for this virtual +machine, ensuring that it does not move. + +[tf-vsphere-vm-resource]: /docs/providers/vsphere/r/virtual_machine.html +[tf-vsphere-cluster-data-source]: /docs/providers/vsphere/d/compute_cluster.html +[tf-vsphere-host-data-source]: /docs/providers/vsphere/d/host.html + +```hcl +data "vsphere_datacenter" "dc" { + name = "dc1" +} + +data "vsphere_datastore" "datastore" { + name = "datastore1" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +data "vsphere_compute_cluster" "cluster" { + name = "cluster1" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +data "vsphere_host" "host" { + name = "esxi1" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +data "vsphere_network" "network" { + name = "network1" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +resource "vsphere_virtual_machine" "vm" { + name = "terraform-test" + resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}" + host_system_id = "${data.vsphere_host.host.id}" + datastore_id = "${data.vsphere_datastore.datastore.id}" + + num_cpus = 2 + memory = 2048 + guest_id = "other3xLinux64Guest" + + network_interface { + network_id = "${data.vsphere_network.network.id}" + } + + disk { + label = "disk0" + size = 20 + } +} + +resource "vsphere_drs_vm_override" "drs_vm_override" { + compute_cluster_id = "${data.vsphere_compute_cluster.cluster.id}" + virtual_machine_id = "${vsphere_virtual_machine.vm.id}" + drs_enabled = false +} +``` + +## Argument Reference + +The following arguments are supported: + +* `compute_cluster_id` - (Required) The [managed object reference + ID][docs-about-morefs] of the cluster to put the override in. Forces a new + resource if changed. + +[docs-about-morefs]: /docs/providers/vsphere/index.html#use-of-managed-object-references-by-the-vsphere-provider + +* `virtual_machine_id` - (Required) The UUID of the virtual machine to create + the override for. Forces a new resource if changed. +* `drs_enabled` - (Optional) Overrides the default DRS setting for this virtual + machine. Can be either `true` or `false`. Default: `false`. +* `drs_automation_level` - (Optional) Overrides the automation level for this virtual + machine in the cluster. Can be one of `manual`, `partiallyAutomated`, or + `fullyAutomated`. Default: `manual`. + +-> **NOTE:** Using this resource _always_ implies an override, even if one of +`drs_enabled` or `drs_automation_level` is omitted. Take note of the defaults +for both options. + +## Attribute Reference + +The only attribute this resource exports is the `id` of the resource, which is +a combination of the [managed object reference ID][docs-about-morefs] of the +cluster, and the UUID of the virtual machine. This is used to look up the +override on subsequent plan and apply operations after the override has been +created. + +## Importing + +An existing override can be [imported][docs-import] into this resource by +supplying both the path to the cluster, and the path to the virtual machine, to +`terraform import`. If no override exists, an error will be given. An example +is below: + +[docs-import]: https://www.terraform.io/docs/import/index.html + +``` +terraform import vsphere_drs_vm_override.drs_vm_override \ + '{"compute_cluster_path": "/dc1/host/cluster1", \ + "virtual_machine_path": "/dc1/vm/srv1"}' +``` diff --git a/website/vsphere.erb b/website/vsphere.erb index 10cc55713..baf1ba423 100644 --- a/website/vsphere.erb +++ b/website/vsphere.erb @@ -70,6 +70,9 @@ > vsphere_compute_cluster + > + vsphere_drs_vm_override +