diff --git a/vsphere/helper_test.go b/vsphere/helper_test.go index 38131004e..d6b2ba7b1 100644 --- a/vsphere/helper_test.go +++ b/vsphere/helper_test.go @@ -969,3 +969,56 @@ func testGetComputeClusterVMDependencyRule(s *terraform.State, resourceName stri return resourceVSphereComputeClusterVMDependencyRuleFindEntry(cluster, name) } + +// testGetComputeClusterVMAffinityRule is a convenience method to fetch a VM +// affinity rule from a (compute) cluster. +func testGetComputeClusterVMAffinityRule(s *terraform.State, resourceName string) (*types.ClusterAffinityRuleSpec, error) { + vars, err := testClientVariablesForResource(s, fmt.Sprintf("%s.%s", resourceVSphereComputeClusterVMAffinityRuleName, resourceName)) + if err != nil { + return nil, err + } + + if vars.resourceID == "" { + return nil, errors.New("resource ID is empty") + } + + clusterID, name, err := resourceVSphereComputeClusterVMAffinityRuleParseID(vars.resourceID) + if err != nil { + return nil, err + } + + cluster, err := clustercomputeresource.FromID(vars.client, clusterID) + if err != nil { + return nil, err + } + + return resourceVSphereComputeClusterVMAffinityRuleFindEntry(cluster, name) +} + +// testGetComputeClusterVMAntiAffinityRule is a convenience method to fetch a +// VM anti-affinity rule from a (compute) cluster. +func testGetComputeClusterVMAntiAffinityRule(s *terraform.State, resourceName string) (*types.ClusterAntiAffinityRuleSpec, error) { + vars, err := testClientVariablesForResource( + s, + fmt.Sprintf("%s.%s", resourceVSphereComputeClusterVMAntiAffinityRuleName, resourceName), + ) + if err != nil { + return nil, err + } + + if vars.resourceID == "" { + return nil, errors.New("resource ID is empty") + } + + clusterID, name, err := resourceVSphereComputeClusterVMAntiAffinityRuleParseID(vars.resourceID) + if err != nil { + return nil, err + } + + cluster, err := clustercomputeresource.FromID(vars.client, clusterID) + if err != nil { + return nil, err + } + + return resourceVSphereComputeClusterVMAntiAffinityRuleFindEntry(cluster, name) +} diff --git a/vsphere/provider.go b/vsphere/provider.go index 1044e0249..08dcbeb69 100644 --- a/vsphere/provider.go +++ b/vsphere/provider.go @@ -88,32 +88,34 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "vsphere_compute_cluster": resourceVSphereComputeCluster(), - "vsphere_compute_cluster_host_group": resourceVSphereComputeClusterHostGroup(), - "vsphere_compute_cluster_vm_dependency_rule": resourceVSphereComputeClusterVMDependencyRule(), - "vsphere_compute_cluster_vm_group": resourceVSphereComputeClusterVMGroup(), - "vsphere_compute_cluster_vm_host_rule": resourceVSphereComputeClusterVMHostRule(), - "vsphere_custom_attribute": resourceVSphereCustomAttribute(), - "vsphere_datacenter": resourceVSphereDatacenter(), - "vsphere_datastore_cluster": resourceVSphereDatastoreCluster(), - "vsphere_distributed_port_group": resourceVSphereDistributedPortGroup(), - "vsphere_distributed_virtual_switch": resourceVSphereDistributedVirtualSwitch(), - "vsphere_drs_vm_override": resourceVSphereDRSVMOverride(), - "vsphere_dpm_host_override": resourceVSphereDPMHostOverride(), - "vsphere_file": resourceVSphereFile(), - "vsphere_folder": resourceVSphereFolder(), - "vsphere_ha_vm_override": resourceVSphereHAVMOverride(), - "vsphere_host_port_group": resourceVSphereHostPortGroup(), - "vsphere_host_virtual_switch": resourceVSphereHostVirtualSwitch(), - "vsphere_license": resourceVSphereLicense(), - "vsphere_tag": resourceVSphereTag(), - "vsphere_tag_category": resourceVSphereTagCategory(), - "vsphere_virtual_disk": resourceVSphereVirtualDisk(), - "vsphere_virtual_machine": resourceVSphereVirtualMachine(), - "vsphere_nas_datastore": resourceVSphereNasDatastore(), - "vsphere_storage_drs_vm_override": resourceVSphereStorageDrsVMOverride(), - "vsphere_vmfs_datastore": resourceVSphereVmfsDatastore(), - "vsphere_virtual_machine_snapshot": resourceVSphereVirtualMachineSnapshot(), + "vsphere_compute_cluster": resourceVSphereComputeCluster(), + "vsphere_compute_cluster_host_group": resourceVSphereComputeClusterHostGroup(), + "vsphere_compute_cluster_vm_affinity_rule": resourceVSphereComputeClusterVMAffinityRule(), + "vsphere_compute_cluster_vm_anti_affinity_rule": resourceVSphereComputeClusterVMAntiAffinityRule(), + "vsphere_compute_cluster_vm_dependency_rule": resourceVSphereComputeClusterVMDependencyRule(), + "vsphere_compute_cluster_vm_group": resourceVSphereComputeClusterVMGroup(), + "vsphere_compute_cluster_vm_host_rule": resourceVSphereComputeClusterVMHostRule(), + "vsphere_custom_attribute": resourceVSphereCustomAttribute(), + "vsphere_datacenter": resourceVSphereDatacenter(), + "vsphere_datastore_cluster": resourceVSphereDatastoreCluster(), + "vsphere_distributed_port_group": resourceVSphereDistributedPortGroup(), + "vsphere_distributed_virtual_switch": resourceVSphereDistributedVirtualSwitch(), + "vsphere_drs_vm_override": resourceVSphereDRSVMOverride(), + "vsphere_dpm_host_override": resourceVSphereDPMHostOverride(), + "vsphere_file": resourceVSphereFile(), + "vsphere_folder": resourceVSphereFolder(), + "vsphere_ha_vm_override": resourceVSphereHAVMOverride(), + "vsphere_host_port_group": resourceVSphereHostPortGroup(), + "vsphere_host_virtual_switch": resourceVSphereHostVirtualSwitch(), + "vsphere_license": resourceVSphereLicense(), + "vsphere_tag": resourceVSphereTag(), + "vsphere_tag_category": resourceVSphereTagCategory(), + "vsphere_virtual_disk": resourceVSphereVirtualDisk(), + "vsphere_virtual_machine": resourceVSphereVirtualMachine(), + "vsphere_nas_datastore": resourceVSphereNasDatastore(), + "vsphere_storage_drs_vm_override": resourceVSphereStorageDrsVMOverride(), + "vsphere_vmfs_datastore": resourceVSphereVmfsDatastore(), + "vsphere_virtual_machine_snapshot": resourceVSphereVirtualMachineSnapshot(), }, DataSourcesMap: map[string]*schema.Resource{ diff --git a/vsphere/resource_vsphere_compute_cluster_vm_affinity_rule.go b/vsphere/resource_vsphere_compute_cluster_vm_affinity_rule.go new file mode 100644 index 000000000..791620432 --- /dev/null +++ b/vsphere/resource_vsphere_compute_cluster_vm_affinity_rule.go @@ -0,0 +1,444 @@ +package vsphere + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "strconv" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "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 resourceVSphereComputeClusterVMAffinityRuleName = "vsphere_compute_cluster_vm_affinity_rule" + +func resourceVSphereComputeClusterVMAffinityRule() *schema.Resource { + return &schema.Resource{ + Create: resourceVSphereComputeClusterVMAffinityRuleCreate, + Read: resourceVSphereComputeClusterVMAffinityRuleRead, + Update: resourceVSphereComputeClusterVMAffinityRuleUpdate, + Delete: resourceVSphereComputeClusterVMAffinityRuleDelete, + Importer: &schema.ResourceImporter{ + State: resourceVSphereComputeClusterVMAffinityRuleImport, + }, + + Schema: map[string]*schema.Schema{ + "compute_cluster_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The managed object ID of the cluster.", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "The unique name of the virtual machine group in the cluster.", + }, + "virtual_machine_ids": { + Type: schema.TypeSet, + Required: true, + Description: "The UUIDs of the virtual machines to run on the same host together.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Enable this rule in the cluster.", + }, + "mandatory": { + Type: schema.TypeBool, + Optional: true, + Description: "When true, prevents any virtual machine operations that may violate this rule.", + }, + }, + } +} + +func resourceVSphereComputeClusterVMAffinityRuleCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] %s: Beginning create", resourceVSphereComputeClusterVMAffinityRuleIDString(d)) + + cluster, _, err := resourceVSphereComputeClusterVMAffinityRuleObjects(d, meta) + if err != nil { + return err + } + + info, err := expandClusterAffinityRuleSpec(d, meta) + if err != nil { + return err + } + spec := &types.ClusterConfigSpecEx{ + RulesSpec: []types.ClusterRuleSpec{ + { + ArrayUpdateSpec: types.ArrayUpdateSpec{ + Operation: types.ArrayUpdateOperationAdd, + }, + Info: info, + }, + }, + } + + if err = clustercomputeresource.Reconfigure(cluster, spec); err != nil { + return err + } + + info, err = resourceVSphereComputeClusterVMAffinityRuleFindEntryByName(cluster, info.Name) + if err != nil { + return err + } + + id, err := resourceVSphereComputeClusterVMAffinityRuleFlattenID(cluster, info.Key) + if err != nil { + return fmt.Errorf("cannot compute ID of created resource: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] %s: Create finished successfully", resourceVSphereComputeClusterVMAffinityRuleIDString(d)) + return resourceVSphereComputeClusterVMAffinityRuleRead(d, meta) +} + +func resourceVSphereComputeClusterVMAffinityRuleRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] %s: Beginning read", resourceVSphereComputeClusterVMAffinityRuleIDString(d)) + + cluster, key, err := resourceVSphereComputeClusterVMAffinityRuleObjects(d, meta) + if err != nil { + return err + } + + info, err := resourceVSphereComputeClusterVMAffinityRuleFindEntry(cluster, key) + 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. This is 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) + } + + if err = flattenClusterAffinityRuleSpec(d, meta, info); err != nil { + return err + } + + log.Printf("[DEBUG] %s: Read completed successfully", resourceVSphereComputeClusterVMAffinityRuleIDString(d)) + return nil +} + +func resourceVSphereComputeClusterVMAffinityRuleUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] %s: Beginning update", resourceVSphereComputeClusterVMAffinityRuleIDString(d)) + + cluster, key, err := resourceVSphereComputeClusterVMAffinityRuleObjects(d, meta) + if err != nil { + return err + } + + info, err := expandClusterAffinityRuleSpec(d, meta) + if err != nil { + return err + } + info.Key = key + + spec := &types.ClusterConfigSpecEx{ + RulesSpec: []types.ClusterRuleSpec{ + { + ArrayUpdateSpec: types.ArrayUpdateSpec{ + Operation: types.ArrayUpdateOperationEdit, + }, + Info: info, + }, + }, + } + + if err := clustercomputeresource.Reconfigure(cluster, spec); err != nil { + return err + } + + log.Printf("[DEBUG] %s: Update finished successfully", resourceVSphereComputeClusterVMAffinityRuleIDString(d)) + return resourceVSphereComputeClusterVMAffinityRuleRead(d, meta) +} + +func resourceVSphereComputeClusterVMAffinityRuleDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] %s: Beginning delete", resourceVSphereComputeClusterVMAffinityRuleIDString(d)) + + cluster, key, err := resourceVSphereComputeClusterVMAffinityRuleObjects(d, meta) + if err != nil { + return err + } + + spec := &types.ClusterConfigSpecEx{ + RulesSpec: []types.ClusterRuleSpec{ + { + ArrayUpdateSpec: types.ArrayUpdateSpec{ + Operation: types.ArrayUpdateOperationRemove, + RemoveKey: key, + }, + }, + }, + } + + if err := clustercomputeresource.Reconfigure(cluster, spec); err != nil { + return err + } + + log.Printf("[DEBUG] %s: Deleted successfully", resourceVSphereComputeClusterVMAffinityRuleIDString(d)) + return nil +} + +func resourceVSphereComputeClusterVMAffinityRuleImport(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") + } + name, ok := data["name"] + if !ok { + return nil, errors.New("missing name in input data") + } + + client, err := resourceVSphereComputeClusterVMAffinityRuleClient(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) + } + + info, err := resourceVSphereComputeClusterVMAffinityRuleFindEntryByName(cluster, name) + if err != nil { + return nil, err + } + + id, err := resourceVSphereComputeClusterVMAffinityRuleFlattenID(cluster, info.Key) + if err != nil { + return nil, fmt.Errorf("cannot compute ID of imported resource: %s", err) + } + d.SetId(id) + return []*schema.ResourceData{d}, nil +} + +// expandClusterAffinityRuleSpec reads certain ResourceData keys and returns a +// ClusterAffinityRuleSpec. +func expandClusterAffinityRuleSpec(d *schema.ResourceData, meta interface{}) (*types.ClusterAffinityRuleSpec, error) { + client, err := resourceVSphereComputeClusterVMGroupClient(meta) + if err != nil { + return nil, err + } + + results, err := virtualmachine.MOIDsForUUIDs( + client, + structure.SliceInterfacesToStrings(d.Get("virtual_machine_ids").(*schema.Set).List()), + ) + if err != nil { + return nil, err + } + + obj := &types.ClusterAffinityRuleSpec{ + ClusterRuleInfo: types.ClusterRuleInfo{ + Enabled: structure.GetBool(d, "enabled"), + Mandatory: structure.GetBool(d, "mandatory"), + Name: d.Get("name").(string), + UserCreated: structure.BoolPtr(true), + }, + Vm: results.ManagedObjectReferences(), + } + return obj, nil +} + +// flattenClusterAffinityRuleSpec saves a ClusterAffinityRuleSpec into the supplied ResourceData. +func flattenClusterAffinityRuleSpec(d *schema.ResourceData, meta interface{}, obj *types.ClusterAffinityRuleSpec) error { + client, err := resourceVSphereComputeClusterVMGroupClient(meta) + if err != nil { + return err + } + + results, err := virtualmachine.UUIDsForManagedObjectReferences( + client, + obj.Vm, + ) + if err != nil { + return err + } + + return structure.SetBatch(d, map[string]interface{}{ + "enabled": obj.Enabled, + "mandatory": obj.Mandatory, + "name": obj.Name, + "virtual_machine_ids": results.UUIDs(), + }) +} + +// resourceVSphereComputeClusterVMAffinityRuleIDString prints a friendly string for the +// vsphere_compute_cluster_vm_affinity_rule resource. +func resourceVSphereComputeClusterVMAffinityRuleIDString(d structure.ResourceIDStringer) string { + return structure.ResourceIDString(d, resourceVSphereComputeClusterVMAffinityRuleName) +} + +// resourceVSphereComputeClusterVMAffinityRuleFlattenID makes an ID for the +// vsphere_compute_cluster_vm_affinity_rule resource. +func resourceVSphereComputeClusterVMAffinityRuleFlattenID(cluster *object.ClusterComputeResource, key int32) (string, error) { + clusterID := cluster.Reference().Value + return strings.Join([]string{clusterID, strconv.Itoa(int(key))}, ":"), nil +} + +// resourceVSphereComputeClusterVMAffinityRuleParseID parses an ID for the +// vsphere_compute_cluster_vm_affinity_rule and outputs its parts. +func resourceVSphereComputeClusterVMAffinityRuleParseID(id string) (string, int32, error) { + parts := strings.SplitN(id, ":", 3) + if len(parts) < 2 { + return "", 0, fmt.Errorf("bad ID %q", id) + } + + key, err := strconv.Atoi(parts[1]) + if err != nil { + return "", 0, fmt.Errorf("bad key in ID %q: %s", parts[1], err) + } + + return parts[0], int32(key), nil +} + +// resourceVSphereComputeClusterVMAffinityRuleFindEntry attempts to locate an +// existing VM affinity rule in a cluster's configuration by key. It's used by the +// resource's read functionality and tests. nil is returned if the entry cannot +// be found. +func resourceVSphereComputeClusterVMAffinityRuleFindEntry( + cluster *object.ClusterComputeResource, + key int32, +) (*types.ClusterAffinityRuleSpec, 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).Rule { + if info.GetClusterRuleInfo().Key == key { + if vmAffinityRuleInfo, ok := info.(*types.ClusterAffinityRuleSpec); ok { + log.Printf("[DEBUG] Found VM affinity rule key %d in cluster %q", key, cluster.Name()) + return vmAffinityRuleInfo, nil + } + return nil, fmt.Errorf("rule key %d in cluster %q is not a VM affinity rule", key, cluster.Name()) + } + } + + log.Printf("[DEBUG] No VM affinity rule key %d found in cluster %q", key, cluster.Name()) + return nil, nil +} + +// resourceVSphereComputeClusterVMAffinityRuleFindEntryByName attempts to locate an +// existing VM affinity rule in a cluster's configuration by name. It differs from +// the standard resourceVSphereComputeClusterVMAffinityRuleFindEntry in that we +// don't allow missing entries, as it's designed to be used in places where we +// don't want to allow for missing entries, such as during creation and import. +func resourceVSphereComputeClusterVMAffinityRuleFindEntryByName( + cluster *object.ClusterComputeResource, + name string, +) (*types.ClusterAffinityRuleSpec, 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).Rule { + if info.GetClusterRuleInfo().Name == name { + if vmAffinityRuleInfo, ok := info.(*types.ClusterAffinityRuleSpec); ok { + log.Printf("[DEBUG] Found VM affinity rule %q in cluster %q", name, cluster.Name()) + return vmAffinityRuleInfo, nil + } + return nil, fmt.Errorf("rule %q in cluster %q is not a VM affinity rule", name, cluster.Name()) + } + } + + return nil, fmt.Errorf("no VM affinity rule %q found in cluster %q", name, cluster.Name()) +} + +// resourceVSphereComputeClusterVMAffinityRuleObjects handles the fetching of the +// cluster and rule key depending on what attributes are available: +// * If the resource ID is available, the data is derived from the ID. +// * If not, only the cluster is retrieved from compute_cluster_id. -1 is +// returned for the key. +func resourceVSphereComputeClusterVMAffinityRuleObjects( + d *schema.ResourceData, + meta interface{}, +) (*object.ClusterComputeResource, int32, error) { + if d.Id() != "" { + return resourceVSphereComputeClusterVMAffinityRuleObjectsFromID(d, meta) + } + return resourceVSphereComputeClusterVMAffinityRuleObjectsFromAttributes(d, meta) +} + +func resourceVSphereComputeClusterVMAffinityRuleObjectsFromAttributes( + d *schema.ResourceData, + meta interface{}, +) (*object.ClusterComputeResource, int32, error) { + return resourceVSphereComputeClusterVMAffinityRuleFetchObjects( + meta, + d.Get("compute_cluster_id").(string), + -1, + ) +} + +func resourceVSphereComputeClusterVMAffinityRuleObjectsFromID( + d structure.ResourceIDStringer, + meta interface{}, +) (*object.ClusterComputeResource, int32, 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, key, err := resourceVSphereComputeClusterVMAffinityRuleParseID(d.Id()) + if err != nil { + return nil, 0, err + } + + return resourceVSphereComputeClusterVMAffinityRuleFetchObjects(meta, clusterID, key) +} + +// resourceVSphereComputeClusterVMAffinityRuleFetchObjects fetches the "objects" +// for a cluster rule. This is currently just the cluster object as the rule +// key a static value and a pass-through - this is to keep its workflow +// consistent with other cluster-dependent resources that derive from +// ArrayUpdateSpec that have managed object as keys, such as VM and host +// overrides. +func resourceVSphereComputeClusterVMAffinityRuleFetchObjects( + meta interface{}, + clusterID string, + key int32, +) (*object.ClusterComputeResource, int32, error) { + client, err := resourceVSphereComputeClusterVMAffinityRuleClient(meta) + if err != nil { + return nil, 0, err + } + + cluster, err := clustercomputeresource.FromID(client, clusterID) + if err != nil { + return nil, 0, fmt.Errorf("cannot locate cluster: %s", err) + } + + return cluster, key, nil +} + +func resourceVSphereComputeClusterVMAffinityRuleClient(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_compute_cluster_vm_affinity_rule_test.go b/vsphere/resource_vsphere_compute_cluster_vm_affinity_rule_test.go new file mode 100644 index 000000000..c64ed2810 --- /dev/null +++ b/vsphere/resource_vsphere_compute_cluster_vm_affinity_rule_test.go @@ -0,0 +1,400 @@ +package vsphere + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "reflect" + "sort" + "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 TestAccResourceVSphereComputeClusterVMAffinityRule_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereComputeClusterVMAffinityRulePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereComputeClusterVMAffinityRuleExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereComputeClusterVMAffinityRuleConfig(2, true), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAffinityRuleMatchBase( + true, + false, + "terraform-test-cluster-affinity-rule", + ), + testAccResourceVSphereComputeClusterVMAffinityRuleMatchMembership(), + ), + }, + }, + }) +} + +func TestAccResourceVSphereComputeClusterVMAffinityRule_updateEnabled(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereComputeClusterVMAffinityRulePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereComputeClusterVMAffinityRuleExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereComputeClusterVMAffinityRuleConfig(2, true), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAffinityRuleMatchBase( + true, + false, + "terraform-test-cluster-affinity-rule", + ), + testAccResourceVSphereComputeClusterVMAffinityRuleMatchMembership(), + ), + }, + { + Config: testAccResourceVSphereComputeClusterVMAffinityRuleConfig(2, false), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAffinityRuleMatchBase( + false, + false, + "terraform-test-cluster-affinity-rule", + ), + testAccResourceVSphereComputeClusterVMAffinityRuleMatchMembership(), + ), + }, + }, + }) +} + +func TestAccResourceVSphereComputeClusterVMAffinityRule_updateCount(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereComputeClusterVMAffinityRulePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereComputeClusterVMAffinityRuleExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereComputeClusterVMAffinityRuleConfig(2, true), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAffinityRuleMatchBase( + true, + false, + "terraform-test-cluster-affinity-rule", + ), + testAccResourceVSphereComputeClusterVMAffinityRuleMatchMembership(), + ), + }, + { + Config: testAccResourceVSphereComputeClusterVMAffinityRuleConfig(3, true), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAffinityRuleMatchBase( + true, + false, + "terraform-test-cluster-affinity-rule", + ), + testAccResourceVSphereComputeClusterVMAffinityRuleMatchMembership(), + ), + }, + }, + }) +} + +func TestAccResourceVSphereComputeClusterVMAffinityRule_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereComputeClusterVMAffinityRulePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereComputeClusterVMAffinityRuleExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereComputeClusterVMAffinityRuleConfig(1, true), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAffinityRuleMatchMembership(), + ), + }, + { + ResourceName: "vsphere_compute_cluster_vm_affinity_rule.cluster_vm_affinity_rule", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + cluster, err := testGetComputeClusterFromDataSource(s, "cluster") + if err != nil { + return "", err + } + + rs, ok := s.RootModule().Resources["vsphere_compute_cluster_vm_affinity_rule.cluster_vm_affinity_rule"] + if !ok { + return "", errors.New("no resource at address vsphere_compute_cluster_vm_affinity_rule.cluster_vm_affinity_rule") + } + name, ok := rs.Primary.Attributes["name"] + if !ok { + return "", errors.New("vsphere_compute_cluster_vm_affinity_rule.cluster_vm_affinity_rule has no name attribute") + } + + m := make(map[string]string) + m["compute_cluster_path"] = cluster.InventoryPath + m["name"] = name + b, err := json.Marshal(m) + if err != nil { + return "", err + } + + return string(b), nil + }, + Config: testAccResourceVSphereComputeClusterVMAffinityRuleConfig(1, true), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAffinityRuleMatchMembership(), + ), + }, + }, + }) +} + +func testAccResourceVSphereComputeClusterVMAffinityRulePreCheck(t *testing.T) { + if os.Getenv("VSPHERE_DATACENTER") == "" { + t.Skip("set VSPHERE_DATACENTER to run vsphere_compute_cluster_vm_affinity_rule acceptance tests") + } + if os.Getenv("VSPHERE_DATASTORE") == "" { + t.Skip("set VSPHERE_DATASTORE to run vsphere_compute_cluster_vm_affinity_rule acceptance tests") + } + if os.Getenv("VSPHERE_CLUSTER") == "" { + t.Skip("set VSPHERE_CLUSTER to run vsphere_compute_cluster_vm_affinity_rule acceptance tests") + } + if os.Getenv("VSPHERE_NETWORK_LABEL_PXE") == "" { + t.Skip("set VSPHERE_NETWORK_LABEL_PXE to run vsphere_compute_cluster_vm_affinity_rule acceptance tests") + } +} + +func testAccResourceVSphereComputeClusterVMAffinityRuleExists(expected bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + info, err := testGetComputeClusterVMAffinityRule(s, "cluster_vm_affinity_rule") + if err != nil { + if expected == false { + if viapi.IsManagedObjectNotFoundError(err) { + // This is not necessarily a missing rule, 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 rule 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("cluster rule missing when expected to exist") + case !expected: + return errors.New("cluster rule still present when expected to be missing") + } + + return nil + } +} + +func testAccResourceVSphereComputeClusterVMAffinityRuleMatchBase( + enabled bool, + mandatory bool, + name string, +) resource.TestCheckFunc { + return func(s *terraform.State) error { + actual, err := testGetComputeClusterVMAffinityRule(s, "cluster_vm_affinity_rule") + if err != nil { + return err + } + + if actual == nil { + return errors.New("cluster rule missing") + } + + expected := &types.ClusterAffinityRuleSpec{ + ClusterRuleInfo: types.ClusterRuleInfo{ + Enabled: structure.BoolPtr(enabled), + Mandatory: structure.BoolPtr(mandatory), + Name: name, + UserCreated: structure.BoolPtr(true), + InCompliance: actual.InCompliance, + Key: actual.Key, + RuleUuid: actual.RuleUuid, + Status: actual.Status, + }, + Vm: actual.Vm, + } + + if !reflect.DeepEqual(expected, actual) { + return spew.Errorf("expected %#v got %#v", expected, actual) + } + + return nil + } +} + +func testAccResourceVSphereComputeClusterVMAffinityRuleMatchMembership() resource.TestCheckFunc { + return func(s *terraform.State) error { + actual, err := testGetComputeClusterVMAffinityRule(s, "cluster_vm_affinity_rule") + if err != nil { + return err + } + + if actual == nil { + return errors.New("cluster rule missing") + } + + vms, err := testAccResourceVSphereComputeClusterVMAffinityRuleMatchMembershipVMIDs(s) + if err != nil { + return err + } + + expectedSort := structure.MoRefSorter(vms) + sort.Sort(expectedSort) + + expected := &types.ClusterAffinityRuleSpec{ + ClusterRuleInfo: actual.ClusterRuleInfo, + Vm: actual.Vm, + } + + actualSort := structure.MoRefSorter(actual.Vm) + sort.Sort(actualSort) + actual.Vm = []types.ManagedObjectReference(actualSort) + + if !reflect.DeepEqual(expected, actual) { + return spew.Errorf("expected %#v got %#v", expected, actual) + } + + return nil + } +} + +func testAccResourceVSphereComputeClusterVMAffinityRuleMatchMembershipVMIDs(s *terraform.State) ([]types.ManagedObjectReference, error) { + var ids []string + if rs, ok := s.RootModule().Resources["vsphere_virtual_machine.vm"]; ok { + ids = []string{rs.Primary.ID} + } else { + ids = testAccResourceVSphereComputeClusterVMAffinityRuleGetMultiple(s) + } + + results, err := virtualmachine.MOIDsForUUIDs(testAccProvider.Meta().(*VSphereClient).vimClient, ids) + if err != nil { + return nil, err + } + return results.ManagedObjectReferences(), nil +} + +func testAccResourceVSphereComputeClusterVMAffinityRuleGetMultiple(s *terraform.State) []string { + var i int + var ids []string + for { + rs, ok := s.RootModule().Resources[fmt.Sprintf("vsphere_virtual_machine.vm.%d", i)] + if !ok { + break + } + ids = append(ids, rs.Primary.ID) + i++ + } + return ids +} + +func testAccResourceVSphereComputeClusterVMAffinityRuleConfig(count int, enabled bool) string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "datastore" { + default = "%s" +} + +variable "cluster" { + default = "%s" +} + +variable "network_label" { + default = "%s" +} + +variable "vm_count" { + default = "%d" +} + +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" { + count = "${var.vm_count}" + name = "terraform-test-${count.index}" + 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_compute_cluster_vm_affinity_rule" "cluster_vm_affinity_rule" { + name = "terraform-test-cluster-affinity-rule" + compute_cluster_id = "${data.vsphere_compute_cluster.cluster.id}" + virtual_machine_ids = ["${vsphere_virtual_machine.vm.*.id}"] + enabled = %t +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_DATASTORE"), + os.Getenv("VSPHERE_CLUSTER"), + os.Getenv("VSPHERE_NETWORK_LABEL_PXE"), + count, + enabled, + ) +} diff --git a/vsphere/resource_vsphere_compute_cluster_vm_anti_affinity_rule.go b/vsphere/resource_vsphere_compute_cluster_vm_anti_affinity_rule.go new file mode 100644 index 000000000..49985ae71 --- /dev/null +++ b/vsphere/resource_vsphere_compute_cluster_vm_anti_affinity_rule.go @@ -0,0 +1,444 @@ +package vsphere + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "strconv" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "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 resourceVSphereComputeClusterVMAntiAffinityRuleName = "vsphere_compute_cluster_vm_anti_affinity_rule" + +func resourceVSphereComputeClusterVMAntiAffinityRule() *schema.Resource { + return &schema.Resource{ + Create: resourceVSphereComputeClusterVMAntiAffinityRuleCreate, + Read: resourceVSphereComputeClusterVMAntiAffinityRuleRead, + Update: resourceVSphereComputeClusterVMAntiAffinityRuleUpdate, + Delete: resourceVSphereComputeClusterVMAntiAffinityRuleDelete, + Importer: &schema.ResourceImporter{ + State: resourceVSphereComputeClusterVMAntiAffinityRuleImport, + }, + + Schema: map[string]*schema.Schema{ + "compute_cluster_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The managed object ID of the cluster.", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "The unique name of the virtual machine group in the cluster.", + }, + "virtual_machine_ids": { + Type: schema.TypeSet, + Required: true, + Description: "The UUIDs of the virtual machines to run on hosts different from each other.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Enable this rule in the cluster.", + }, + "mandatory": { + Type: schema.TypeBool, + Optional: true, + Description: "When true, prevents any virtual machine operations that may violate this rule.", + }, + }, + } +} + +func resourceVSphereComputeClusterVMAntiAffinityRuleCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] %s: Beginning create", resourceVSphereComputeClusterVMAntiAffinityRuleIDString(d)) + + cluster, _, err := resourceVSphereComputeClusterVMAntiAffinityRuleObjects(d, meta) + if err != nil { + return err + } + + info, err := expandClusterAntiAffinityRuleSpec(d, meta) + if err != nil { + return err + } + spec := &types.ClusterConfigSpecEx{ + RulesSpec: []types.ClusterRuleSpec{ + { + ArrayUpdateSpec: types.ArrayUpdateSpec{ + Operation: types.ArrayUpdateOperationAdd, + }, + Info: info, + }, + }, + } + + if err = clustercomputeresource.Reconfigure(cluster, spec); err != nil { + return err + } + + info, err = resourceVSphereComputeClusterVMAntiAffinityRuleFindEntryByName(cluster, info.Name) + if err != nil { + return err + } + + id, err := resourceVSphereComputeClusterVMAntiAffinityRuleFlattenID(cluster, info.Key) + if err != nil { + return fmt.Errorf("cannot compute ID of created resource: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] %s: Create finished successfully", resourceVSphereComputeClusterVMAntiAffinityRuleIDString(d)) + return resourceVSphereComputeClusterVMAntiAffinityRuleRead(d, meta) +} + +func resourceVSphereComputeClusterVMAntiAffinityRuleRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] %s: Beginning read", resourceVSphereComputeClusterVMAntiAffinityRuleIDString(d)) + + cluster, key, err := resourceVSphereComputeClusterVMAntiAffinityRuleObjects(d, meta) + if err != nil { + return err + } + + info, err := resourceVSphereComputeClusterVMAntiAffinityRuleFindEntry(cluster, key) + 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. This is 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) + } + + if err = flattenClusterAntiAffinityRuleSpec(d, meta, info); err != nil { + return err + } + + log.Printf("[DEBUG] %s: Read completed successfully", resourceVSphereComputeClusterVMAntiAffinityRuleIDString(d)) + return nil +} + +func resourceVSphereComputeClusterVMAntiAffinityRuleUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] %s: Beginning update", resourceVSphereComputeClusterVMAntiAffinityRuleIDString(d)) + + cluster, key, err := resourceVSphereComputeClusterVMAntiAffinityRuleObjects(d, meta) + if err != nil { + return err + } + + info, err := expandClusterAntiAffinityRuleSpec(d, meta) + if err != nil { + return err + } + info.Key = key + + spec := &types.ClusterConfigSpecEx{ + RulesSpec: []types.ClusterRuleSpec{ + { + ArrayUpdateSpec: types.ArrayUpdateSpec{ + Operation: types.ArrayUpdateOperationEdit, + }, + Info: info, + }, + }, + } + + if err := clustercomputeresource.Reconfigure(cluster, spec); err != nil { + return err + } + + log.Printf("[DEBUG] %s: Update finished successfully", resourceVSphereComputeClusterVMAntiAffinityRuleIDString(d)) + return resourceVSphereComputeClusterVMAntiAffinityRuleRead(d, meta) +} + +func resourceVSphereComputeClusterVMAntiAffinityRuleDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] %s: Beginning delete", resourceVSphereComputeClusterVMAntiAffinityRuleIDString(d)) + + cluster, key, err := resourceVSphereComputeClusterVMAntiAffinityRuleObjects(d, meta) + if err != nil { + return err + } + + spec := &types.ClusterConfigSpecEx{ + RulesSpec: []types.ClusterRuleSpec{ + { + ArrayUpdateSpec: types.ArrayUpdateSpec{ + Operation: types.ArrayUpdateOperationRemove, + RemoveKey: key, + }, + }, + }, + } + + if err := clustercomputeresource.Reconfigure(cluster, spec); err != nil { + return err + } + + log.Printf("[DEBUG] %s: Deleted successfully", resourceVSphereComputeClusterVMAntiAffinityRuleIDString(d)) + return nil +} + +func resourceVSphereComputeClusterVMAntiAffinityRuleImport(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") + } + name, ok := data["name"] + if !ok { + return nil, errors.New("missing name in input data") + } + + client, err := resourceVSphereComputeClusterVMAntiAffinityRuleClient(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) + } + + info, err := resourceVSphereComputeClusterVMAntiAffinityRuleFindEntryByName(cluster, name) + if err != nil { + return nil, err + } + + id, err := resourceVSphereComputeClusterVMAntiAffinityRuleFlattenID(cluster, info.Key) + if err != nil { + return nil, fmt.Errorf("cannot compute ID of imported resource: %s", err) + } + d.SetId(id) + return []*schema.ResourceData{d}, nil +} + +// expandClusterAntiAffinityRuleSpec reads certain ResourceData keys and returns a +// ClusterAntiAffinityRuleSpec. +func expandClusterAntiAffinityRuleSpec(d *schema.ResourceData, meta interface{}) (*types.ClusterAntiAffinityRuleSpec, error) { + client, err := resourceVSphereComputeClusterVMGroupClient(meta) + if err != nil { + return nil, err + } + + results, err := virtualmachine.MOIDsForUUIDs( + client, + structure.SliceInterfacesToStrings(d.Get("virtual_machine_ids").(*schema.Set).List()), + ) + if err != nil { + return nil, err + } + + obj := &types.ClusterAntiAffinityRuleSpec{ + ClusterRuleInfo: types.ClusterRuleInfo{ + Enabled: structure.GetBool(d, "enabled"), + Mandatory: structure.GetBool(d, "mandatory"), + Name: d.Get("name").(string), + UserCreated: structure.BoolPtr(true), + }, + Vm: results.ManagedObjectReferences(), + } + return obj, nil +} + +// flattenClusterAntiAffinityRuleSpec saves a ClusterAntiAffinityRuleSpec into the supplied ResourceData. +func flattenClusterAntiAffinityRuleSpec(d *schema.ResourceData, meta interface{}, obj *types.ClusterAntiAffinityRuleSpec) error { + client, err := resourceVSphereComputeClusterVMGroupClient(meta) + if err != nil { + return err + } + + results, err := virtualmachine.UUIDsForManagedObjectReferences( + client, + obj.Vm, + ) + if err != nil { + return err + } + + return structure.SetBatch(d, map[string]interface{}{ + "enabled": obj.Enabled, + "mandatory": obj.Mandatory, + "name": obj.Name, + "virtual_machine_ids": results.UUIDs(), + }) +} + +// resourceVSphereComputeClusterVMAntiAffinityRuleIDString prints a friendly string for the +// vsphere_compute_cluster_vm_anti_affinity_rule resource. +func resourceVSphereComputeClusterVMAntiAffinityRuleIDString(d structure.ResourceIDStringer) string { + return structure.ResourceIDString(d, resourceVSphereComputeClusterVMAntiAffinityRuleName) +} + +// resourceVSphereComputeClusterVMAntiAffinityRuleFlattenID makes an ID for the +// vsphere_compute_cluster_vm_anti_affinity_rule resource. +func resourceVSphereComputeClusterVMAntiAffinityRuleFlattenID(cluster *object.ClusterComputeResource, key int32) (string, error) { + clusterID := cluster.Reference().Value + return strings.Join([]string{clusterID, strconv.Itoa(int(key))}, ":"), nil +} + +// resourceVSphereComputeClusterVMAntiAffinityRuleParseID parses an ID for the +// vsphere_compute_cluster_vm_anti_affinity_rule and outputs its parts. +func resourceVSphereComputeClusterVMAntiAffinityRuleParseID(id string) (string, int32, error) { + parts := strings.SplitN(id, ":", 3) + if len(parts) < 2 { + return "", 0, fmt.Errorf("bad ID %q", id) + } + + key, err := strconv.Atoi(parts[1]) + if err != nil { + return "", 0, fmt.Errorf("bad key in ID %q: %s", parts[1], err) + } + + return parts[0], int32(key), nil +} + +// resourceVSphereComputeClusterVMAntiAffinityRuleFindEntry attempts to locate an +// existing VM anti-affinity rule in a cluster's configuration by key. It's used by the +// resource's read functionality and tests. nil is returned if the entry cannot +// be found. +func resourceVSphereComputeClusterVMAntiAffinityRuleFindEntry( + cluster *object.ClusterComputeResource, + key int32, +) (*types.ClusterAntiAffinityRuleSpec, 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).Rule { + if info.GetClusterRuleInfo().Key == key { + if vmAffinityRuleInfo, ok := info.(*types.ClusterAntiAffinityRuleSpec); ok { + log.Printf("[DEBUG] Found VM anti-affinity rule key %d in cluster %q", key, cluster.Name()) + return vmAffinityRuleInfo, nil + } + return nil, fmt.Errorf("rule key %d in cluster %q is not a VM anti-affinity rule", key, cluster.Name()) + } + } + + log.Printf("[DEBUG] No VM anti-affinity rule key %d found in cluster %q", key, cluster.Name()) + return nil, nil +} + +// resourceVSphereComputeClusterVMAntiAffinityRuleFindEntryByName attempts to locate an +// existing VM anti-affinity rule in a cluster's configuration by name. It differs from +// the standard resourceVSphereComputeClusterVMAntiAffinityRuleFindEntry in that we +// don't allow missing entries, as it's designed to be used in places where we +// don't want to allow for missing entries, such as during creation and import. +func resourceVSphereComputeClusterVMAntiAffinityRuleFindEntryByName( + cluster *object.ClusterComputeResource, + name string, +) (*types.ClusterAntiAffinityRuleSpec, 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).Rule { + if info.GetClusterRuleInfo().Name == name { + if vmAffinityRuleInfo, ok := info.(*types.ClusterAntiAffinityRuleSpec); ok { + log.Printf("[DEBUG] Found VM anti-affinity rule %q in cluster %q", name, cluster.Name()) + return vmAffinityRuleInfo, nil + } + return nil, fmt.Errorf("rule %q in cluster %q is not a VM anti-affinity rule", name, cluster.Name()) + } + } + + return nil, fmt.Errorf("no VM anti-affinity rule %q found in cluster %q", name, cluster.Name()) +} + +// resourceVSphereComputeClusterVMAntiAffinityRuleObjects handles the fetching of the +// cluster and rule key depending on what attributes are available: +// * If the resource ID is available, the data is derived from the ID. +// * If not, only the cluster is retrieved from compute_cluster_id. -1 is +// returned for the key. +func resourceVSphereComputeClusterVMAntiAffinityRuleObjects( + d *schema.ResourceData, + meta interface{}, +) (*object.ClusterComputeResource, int32, error) { + if d.Id() != "" { + return resourceVSphereComputeClusterVMAntiAffinityRuleObjectsFromID(d, meta) + } + return resourceVSphereComputeClusterVMAntiAffinityRuleObjectsFromAttributes(d, meta) +} + +func resourceVSphereComputeClusterVMAntiAffinityRuleObjectsFromAttributes( + d *schema.ResourceData, + meta interface{}, +) (*object.ClusterComputeResource, int32, error) { + return resourceVSphereComputeClusterVMAntiAffinityRuleFetchObjects( + meta, + d.Get("compute_cluster_id").(string), + -1, + ) +} + +func resourceVSphereComputeClusterVMAntiAffinityRuleObjectsFromID( + d structure.ResourceIDStringer, + meta interface{}, +) (*object.ClusterComputeResource, int32, 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, key, err := resourceVSphereComputeClusterVMAntiAffinityRuleParseID(d.Id()) + if err != nil { + return nil, 0, err + } + + return resourceVSphereComputeClusterVMAntiAffinityRuleFetchObjects(meta, clusterID, key) +} + +// resourceVSphereComputeClusterVMAntiAffinityRuleFetchObjects fetches the "objects" +// for a cluster rule. This is currently just the cluster object as the rule +// key a static value and a pass-through - this is to keep its workflow +// consistent with other cluster-dependent resources that derive from +// ArrayUpdateSpec that have managed object as keys, such as VM and host +// overrides. +func resourceVSphereComputeClusterVMAntiAffinityRuleFetchObjects( + meta interface{}, + clusterID string, + key int32, +) (*object.ClusterComputeResource, int32, error) { + client, err := resourceVSphereComputeClusterVMAntiAffinityRuleClient(meta) + if err != nil { + return nil, 0, err + } + + cluster, err := clustercomputeresource.FromID(client, clusterID) + if err != nil { + return nil, 0, fmt.Errorf("cannot locate cluster: %s", err) + } + + return cluster, key, nil +} + +func resourceVSphereComputeClusterVMAntiAffinityRuleClient(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_compute_cluster_vm_anti_affinity_rule_test.go b/vsphere/resource_vsphere_compute_cluster_vm_anti_affinity_rule_test.go new file mode 100644 index 000000000..af19060b4 --- /dev/null +++ b/vsphere/resource_vsphere_compute_cluster_vm_anti_affinity_rule_test.go @@ -0,0 +1,400 @@ +package vsphere + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "reflect" + "sort" + "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 TestAccResourceVSphereComputeClusterVMAntiAffinityRule_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereComputeClusterVMAntiAffinityRulePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereComputeClusterVMAntiAffinityRuleExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereComputeClusterVMAntiAffinityRuleConfig(2, true), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAntiAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchBase( + true, + false, + "terraform-test-cluster-affinity-rule", + ), + testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchMembership(), + ), + }, + }, + }) +} + +func TestAccResourceVSphereComputeClusterVMAntiAffinityRule_updateEnabled(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereComputeClusterVMAntiAffinityRulePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereComputeClusterVMAntiAffinityRuleExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereComputeClusterVMAntiAffinityRuleConfig(2, true), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAntiAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchBase( + true, + false, + "terraform-test-cluster-affinity-rule", + ), + testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchMembership(), + ), + }, + { + Config: testAccResourceVSphereComputeClusterVMAntiAffinityRuleConfig(2, false), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAntiAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchBase( + false, + false, + "terraform-test-cluster-affinity-rule", + ), + testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchMembership(), + ), + }, + }, + }) +} + +func TestAccResourceVSphereComputeClusterVMAntiAffinityRule_updateCount(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereComputeClusterVMAntiAffinityRulePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereComputeClusterVMAntiAffinityRuleExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereComputeClusterVMAntiAffinityRuleConfig(2, true), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAntiAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchBase( + true, + false, + "terraform-test-cluster-affinity-rule", + ), + testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchMembership(), + ), + }, + { + Config: testAccResourceVSphereComputeClusterVMAntiAffinityRuleConfig(3, true), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAntiAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchBase( + true, + false, + "terraform-test-cluster-affinity-rule", + ), + testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchMembership(), + ), + }, + }, + }) +} + +func TestAccResourceVSphereComputeClusterVMAntiAffinityRule_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereComputeClusterVMAntiAffinityRulePreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereComputeClusterVMAntiAffinityRuleExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereComputeClusterVMAntiAffinityRuleConfig(1, true), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAntiAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchMembership(), + ), + }, + { + ResourceName: "vsphere_compute_cluster_vm_anti_affinity_rule.cluster_vm_anti_affinity_rule", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + cluster, err := testGetComputeClusterFromDataSource(s, "cluster") + if err != nil { + return "", err + } + + rs, ok := s.RootModule().Resources["vsphere_compute_cluster_vm_anti_affinity_rule.cluster_vm_anti_affinity_rule"] + if !ok { + return "", errors.New("no resource at address vsphere_compute_cluster_vm_anti_affinity_rule.cluster_vm_anti_affinity_rule") + } + name, ok := rs.Primary.Attributes["name"] + if !ok { + return "", errors.New("vsphere_compute_cluster_vm_anti_affinity_rule.cluster_vm_anti_affinity_rule has no name attribute") + } + + m := make(map[string]string) + m["compute_cluster_path"] = cluster.InventoryPath + m["name"] = name + b, err := json.Marshal(m) + if err != nil { + return "", err + } + + return string(b), nil + }, + Config: testAccResourceVSphereComputeClusterVMAntiAffinityRuleConfig(1, true), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereComputeClusterVMAntiAffinityRuleExists(true), + testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchMembership(), + ), + }, + }, + }) +} + +func testAccResourceVSphereComputeClusterVMAntiAffinityRulePreCheck(t *testing.T) { + if os.Getenv("VSPHERE_DATACENTER") == "" { + t.Skip("set VSPHERE_DATACENTER to run vsphere_compute_cluster_vm_anti_affinity_rule acceptance tests") + } + if os.Getenv("VSPHERE_DATASTORE") == "" { + t.Skip("set VSPHERE_DATASTORE to run vsphere_compute_cluster_vm_anti_affinity_rule acceptance tests") + } + if os.Getenv("VSPHERE_CLUSTER") == "" { + t.Skip("set VSPHERE_CLUSTER to run vsphere_compute_cluster_vm_anti_affinity_rule acceptance tests") + } + if os.Getenv("VSPHERE_NETWORK_LABEL_PXE") == "" { + t.Skip("set VSPHERE_NETWORK_LABEL_PXE to run vsphere_compute_cluster_vm_anti_affinity_rule acceptance tests") + } +} + +func testAccResourceVSphereComputeClusterVMAntiAffinityRuleExists(expected bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + info, err := testGetComputeClusterVMAntiAffinityRule(s, "cluster_vm_anti_affinity_rule") + if err != nil { + if expected == false { + if viapi.IsManagedObjectNotFoundError(err) { + // This is not necessarily a missing rule, 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 rule 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("cluster rule missing when expected to exist") + case !expected: + return errors.New("cluster rule still present when expected to be missing") + } + + return nil + } +} + +func testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchBase( + enabled bool, + mandatory bool, + name string, +) resource.TestCheckFunc { + return func(s *terraform.State) error { + actual, err := testGetComputeClusterVMAntiAffinityRule(s, "cluster_vm_anti_affinity_rule") + if err != nil { + return err + } + + if actual == nil { + return errors.New("cluster rule missing") + } + + expected := &types.ClusterAntiAffinityRuleSpec{ + ClusterRuleInfo: types.ClusterRuleInfo{ + Enabled: structure.BoolPtr(enabled), + Mandatory: structure.BoolPtr(mandatory), + Name: name, + UserCreated: structure.BoolPtr(true), + InCompliance: actual.InCompliance, + Key: actual.Key, + RuleUuid: actual.RuleUuid, + Status: actual.Status, + }, + Vm: actual.Vm, + } + + if !reflect.DeepEqual(expected, actual) { + return spew.Errorf("expected %#v got %#v", expected, actual) + } + + return nil + } +} + +func testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchMembership() resource.TestCheckFunc { + return func(s *terraform.State) error { + actual, err := testGetComputeClusterVMAntiAffinityRule(s, "cluster_vm_anti_affinity_rule") + if err != nil { + return err + } + + if actual == nil { + return errors.New("cluster rule missing") + } + + vms, err := testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchMembershipVMIDs(s) + if err != nil { + return err + } + + expectedSort := structure.MoRefSorter(vms) + sort.Sort(expectedSort) + + expected := &types.ClusterAntiAffinityRuleSpec{ + ClusterRuleInfo: actual.ClusterRuleInfo, + Vm: actual.Vm, + } + + actualSort := structure.MoRefSorter(actual.Vm) + sort.Sort(actualSort) + actual.Vm = []types.ManagedObjectReference(actualSort) + + if !reflect.DeepEqual(expected, actual) { + return spew.Errorf("expected %#v got %#v", expected, actual) + } + + return nil + } +} + +func testAccResourceVSphereComputeClusterVMAntiAffinityRuleMatchMembershipVMIDs(s *terraform.State) ([]types.ManagedObjectReference, error) { + var ids []string + if rs, ok := s.RootModule().Resources["vsphere_virtual_machine.vm"]; ok { + ids = []string{rs.Primary.ID} + } else { + ids = testAccResourceVSphereComputeClusterVMAntiAffinityRuleGetMultiple(s) + } + + results, err := virtualmachine.MOIDsForUUIDs(testAccProvider.Meta().(*VSphereClient).vimClient, ids) + if err != nil { + return nil, err + } + return results.ManagedObjectReferences(), nil +} + +func testAccResourceVSphereComputeClusterVMAntiAffinityRuleGetMultiple(s *terraform.State) []string { + var i int + var ids []string + for { + rs, ok := s.RootModule().Resources[fmt.Sprintf("vsphere_virtual_machine.vm.%d", i)] + if !ok { + break + } + ids = append(ids, rs.Primary.ID) + i++ + } + return ids +} + +func testAccResourceVSphereComputeClusterVMAntiAffinityRuleConfig(count int, enabled bool) string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "datastore" { + default = "%s" +} + +variable "cluster" { + default = "%s" +} + +variable "network_label" { + default = "%s" +} + +variable "vm_count" { + default = "%d" +} + +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" { + count = "${var.vm_count}" + name = "terraform-test-${count.index}" + 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_compute_cluster_vm_anti_affinity_rule" "cluster_vm_anti_affinity_rule" { + name = "terraform-test-cluster-affinity-rule" + compute_cluster_id = "${data.vsphere_compute_cluster.cluster.id}" + virtual_machine_ids = ["${vsphere_virtual_machine.vm.*.id}"] + enabled = %t +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_DATASTORE"), + os.Getenv("VSPHERE_CLUSTER"), + os.Getenv("VSPHERE_NETWORK_LABEL_PXE"), + count, + enabled, + ) +} diff --git a/vsphere/resource_vsphere_compute_cluster_vm_group_test.go b/vsphere/resource_vsphere_compute_cluster_vm_group_test.go index e97eb6f23..3997d4566 100644 --- a/vsphere/resource_vsphere_compute_cluster_vm_group_test.go +++ b/vsphere/resource_vsphere_compute_cluster_vm_group_test.go @@ -122,16 +122,16 @@ func TestAccResourceVSphereComputeClusterVMGroup_import(t *testing.T) { func testAccResourceVSphereComputeClusterVMGroupPreCheck(t *testing.T) { if os.Getenv("VSPHERE_DATACENTER") == "" { - t.Skip("set VSPHERE_DATACENTER to run vsphere_storage_drs_vm_override acceptance tests") + t.Skip("set VSPHERE_DATACENTER to run vsphere_compute_cluster_vm_group acceptance tests") } if os.Getenv("VSPHERE_DATASTORE") == "" { - t.Skip("set VSPHERE_DATASTORE to run vsphere_storage_drs_vm_override acceptance tests") + t.Skip("set VSPHERE_DATASTORE to run vsphere_compute_cluster_vm_group acceptance tests") } if os.Getenv("VSPHERE_CLUSTER") == "" { - t.Skip("set VSPHERE_CLUSTER to run vsphere_storage_drs_vm_override acceptance tests") + t.Skip("set VSPHERE_CLUSTER to run vsphere_compute_cluster_vm_group 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") + t.Skip("set VSPHERE_NETWORK_LABEL_PXE to run vsphere_compute_cluster_vm_group acceptance tests") } } diff --git a/vsphere/resource_vsphere_drs_vm_override_test.go b/vsphere/resource_vsphere_drs_vm_override_test.go index b86234f34..d0fd4ca33 100644 --- a/vsphere/resource_vsphere_drs_vm_override_test.go +++ b/vsphere/resource_vsphere_drs_vm_override_test.go @@ -136,16 +136,16 @@ func TestAccResourceVSphereDRSVMOverride_import(t *testing.T) { func testAccResourceVSphereDRSVMOverridePreCheck(t *testing.T) { if os.Getenv("VSPHERE_DATACENTER") == "" { - t.Skip("set VSPHERE_DATACENTER to run vsphere_storage_drs_vm_override acceptance tests") + t.Skip("set VSPHERE_DATACENTER to run vsphere_drs_vm_override acceptance tests") } if os.Getenv("VSPHERE_DATASTORE") == "" { - t.Skip("set VSPHERE_DATASTORE to run vsphere_storage_drs_vm_override acceptance tests") + t.Skip("set VSPHERE_DATASTORE to run vsphere_drs_vm_override acceptance tests") } if os.Getenv("VSPHERE_CLUSTER") == "" { - t.Skip("set VSPHERE_CLUSTER to run vsphere_storage_drs_vm_override acceptance tests") + t.Skip("set VSPHERE_CLUSTER to run vsphere_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") + t.Skip("set VSPHERE_NETWORK_LABEL_PXE to run vsphere_drs_vm_override acceptance tests") } } diff --git a/vsphere/resource_vsphere_ha_vm_override_test.go b/vsphere/resource_vsphere_ha_vm_override_test.go index 2d8014bcb..69e850f17 100644 --- a/vsphere/resource_vsphere_ha_vm_override_test.go +++ b/vsphere/resource_vsphere_ha_vm_override_test.go @@ -244,16 +244,16 @@ func TestAccResourceVSphereHAVMOverride_import(t *testing.T) { func testAccResourceVSphereHAVMOverridePreCheck(t *testing.T) { if os.Getenv("VSPHERE_DATACENTER") == "" { - t.Skip("set VSPHERE_DATACENTER to run vsphere_storage_drs_vm_override acceptance tests") + t.Skip("set VSPHERE_DATACENTER to run vsphere_ha_vm_override acceptance tests") } if os.Getenv("VSPHERE_DATASTORE") == "" { - t.Skip("set VSPHERE_DATASTORE to run vsphere_storage_drs_vm_override acceptance tests") + t.Skip("set VSPHERE_DATASTORE to run vsphere_ha_vm_override acceptance tests") } if os.Getenv("VSPHERE_CLUSTER") == "" { - t.Skip("set VSPHERE_CLUSTER to run vsphere_storage_drs_vm_override acceptance tests") + t.Skip("set VSPHERE_CLUSTER to run vsphere_ha_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") + t.Skip("set VSPHERE_NETWORK_LABEL_PXE to run vsphere_ha_vm_override acceptance tests") } } diff --git a/website/docs/r/compute_cluster_vm_affinity_rule.html.markdown b/website/docs/r/compute_cluster_vm_affinity_rule.html.markdown new file mode 100644 index 000000000..6ae0a5f76 --- /dev/null +++ b/website/docs/r/compute_cluster_vm_affinity_rule.html.markdown @@ -0,0 +1,136 @@ +--- +layout: "vsphere" +page_title: "VMware vSphere: vsphere_compute_cluster_vm_affinity_rule" +sidebar_current: "docs-vsphere-resource-compute-cluster-vm-affinity-rule" +description: |- + Provides a VMware vSphere cluster virtual machine affinity rule. This can be used to manage rules to tell virtual machines to run on the same host. +--- + +# vsphere\_compute\_cluster\_vm\_affinity\_rule + +The `vsphere_compute_cluster_vm_affinity_rule` resource can be used to manage +VM affinity rules in a cluster, either created by the +[`vsphere_compute_cluster`][tf-vsphere-cluster-resource] resource or looked up +by the [`vsphere_compute_cluster`][tf-vsphere-cluster-data-source] data source. + +[tf-vsphere-cluster-resource]: /docs/providers/vsphere/r/compute_cluster.html +[tf-vsphere-cluster-data-source]: /docs/providers/vsphere/d/compute_cluster.html + +This rule can be used to tell a set to virtual machines to run together on a +single host within a cluster. When configured, DRS will make a best effort to +ensure that the virtual machines run on the same host, or prevent any operation +that would keep that from happening, depending on the value of the +[`mandatory`](#mandatory) flag. + +-> Keep in mind that this rule can only be used to tell VMs to run together on +a _non-specific_ host - it can't be used to pin VMs to a host. For that, see +the +[`vsphere_compute_cluster_vm_host_rule`][tf-vsphere-cluster-vm-host-rule-resource] +resource. + +[tf-vsphere-cluster-vm-host-rule-resource]: /docs/providers/vsphere/r/compute_cluster_vm_host_rule.html + +~> **NOTE:** This resource requires vCenter and is not available on direct ESXi +connections. + +~> **NOTE:** vSphere DRS requires a vSphere Enterprise Plus license. + +## Example Usage + +The example below creates two virtual machines in a cluster using the +[`vsphere_virtual_machine`][tf-vsphere-vm-resource] resource, creating the +virtual machines in the cluster looked up by the +[`vsphere_compute_cluster`][tf-vsphere-cluster-data-source] data source. It +then creates an affinity rule for these two virtual machines, ensuring they +will run on the same host whenever possible. + +[tf-vsphere-vm-resource]: /docs/providers/vsphere/r/virtual_machine.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_network" "network" { + name = "network1" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +resource "vsphere_virtual_machine" "vm" { + count = 2 + name = "terraform-test-${count.index}" + 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" + + network_interface { + network_id = "${data.vsphere_network.network.id}" + } + + disk { + label = "disk0" + size = 20 + } +} + +resource "vsphere_compute_cluster_vm_affinity_rule" "cluster_vm_affinity_rule" { + name = "terraform-test-cluster-vm-affinity-rule" + compute_cluster_id = "${data.vsphere_compute_cluster.cluster.id}" + virtual_machine_ids = ["${vsphere_virtual_machine.vm.*.id}"] +} +``` + +## 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 group in. Forces a new + resource if changed. + +[docs-about-morefs]: /docs/providers/vsphere/index.html#use-of-managed-object-references-by-the-vsphere-provider + +* `name` - (Required) The name of the rule. This must be unique in the cluster. +* `virtual_machine_ids` - (Required) The UUIDs of the virtual machines to run + on the same host together. +* `enabled` - (Optional) Enable this rule in the cluster. Default: `true`. +* `mandatory` - (Optional) When this value is `true`, prevents any virtual + machine operations that may violate this rule. Default: `false`. + +~> **NOTE:** The namespace for rule names on this resource (defined by the +[`name`](#name) argument) is shared with all rules in the cluster - consider +this when naming your rules. + +## 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 rule's key within the cluster configuration. + +## Importing + +An existing rule can be [imported][docs-import] into this resource by supplying +both the path to the cluster, and the name the rule. If the name or cluster is +not found, or if the rule is of a different type, an error will be returned. An +example is below: + +[docs-import]: https://www.terraform.io/docs/import/index.html + +``` +terraform import vsphere_compute_cluster_vm_affinity_rule.cluster_vm_affinity_rule \ + '{"compute_cluster_path": "/dc1/host/cluster1", \ + "name": "terraform-test-cluster-vm-affinity-rule"}' +``` diff --git a/website/docs/r/compute_cluster_vm_anti_affinity_rule.html.markdown b/website/docs/r/compute_cluster_vm_anti_affinity_rule.html.markdown new file mode 100644 index 000000000..d2ae4f5a9 --- /dev/null +++ b/website/docs/r/compute_cluster_vm_anti_affinity_rule.html.markdown @@ -0,0 +1,137 @@ +--- +layout: "vsphere" +page_title: "VMware vSphere: vsphere_compute_cluster_vm_anti_affinity_rule" +sidebar_current: "docs-vsphere-resource-compute-cluster-vm-anti-affinity-rule" +description: |- + Provides a VMware vSphere cluster virtual machine anti-affinity rule. This can be used to manage rules to tell virtual machines to run on separate hosts. +--- + +# vsphere\_compute\_cluster\_vm\_anti\_affinity\_rule + +The `vsphere_compute_cluster_vm_anti_affinity_rule` resource can be used to +manage VM anti-affinity rules in a cluster, either created by the +[`vsphere_compute_cluster`][tf-vsphere-cluster-resource] resource or looked up +by the [`vsphere_compute_cluster`][tf-vsphere-cluster-data-source] data source. + +[tf-vsphere-cluster-resource]: /docs/providers/vsphere/r/compute_cluster.html +[tf-vsphere-cluster-data-source]: /docs/providers/vsphere/d/compute_cluster.html + +This rule can be used to tell a set to virtual machines to run on different +hosts within a cluster, useful for preventing single points of failure in +application cluster scenarios. When configured, DRS will make a best effort to +ensure that the virtual machines run on different hosts, or prevent any +operation that would keep that from happening, depending on the value of the +[`mandatory`](#mandatory) flag. + +-> Keep in mind that this rule can only be used to tell VMs to run separately +on _non-specific_ hosts - specific hosts cannot be specified with this rule. +For that, see the +[`vsphere_compute_cluster_vm_host_rule`][tf-vsphere-cluster-vm-host-rule-resource] +resource. + +[tf-vsphere-cluster-vm-host-rule-resource]: /docs/providers/vsphere/r/compute_cluster_vm_host_rule.html + +~> **NOTE:** This resource requires vCenter and is not available on direct ESXi +connections. + +~> **NOTE:** vSphere DRS requires a vSphere Enterprise Plus license. + +## Example Usage + +The example below creates two virtual machines in a cluster using the +[`vsphere_virtual_machine`][tf-vsphere-vm-resource] resource, creating the +virtual machines in the cluster looked up by the +[`vsphere_compute_cluster`][tf-vsphere-cluster-data-source] data source. It +then creates an anti-affinity rule for these two virtual machines, ensuring +they will run on different hosts whenever possible. + +[tf-vsphere-vm-resource]: /docs/providers/vsphere/r/virtual_machine.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_network" "network" { + name = "network1" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +resource "vsphere_virtual_machine" "vm" { + count = 2 + name = "terraform-test-${count.index}" + 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" + + network_interface { + network_id = "${data.vsphere_network.network.id}" + } + + disk { + label = "disk0" + size = 20 + } +} + +resource "vsphere_compute_cluster_vm_anti_affinity_rule" "cluster_vm_anti_affinity_rule" { + name = "terraform-test-cluster-vm-anti-affinity-rule" + compute_cluster_id = "${data.vsphere_compute_cluster.cluster.id}" + virtual_machine_ids = ["${vsphere_virtual_machine.vm.*.id}"] +} +``` + +## 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 group in. Forces a new + resource if changed. + +[docs-about-morefs]: /docs/providers/vsphere/index.html#use-of-managed-object-references-by-the-vsphere-provider + +* `name` - (Required) The name of the rule. This must be unique in the cluster. +* `virtual_machine_ids` - (Required) The UUIDs of the virtual machines to run + on hosts different from each other. +* `enabled` - (Optional) Enable this rule in the cluster. Default: `true`. +* `mandatory` - (Optional) When this value is `true`, prevents any virtual + machine operations that may violate this rule. Default: `false`. + +~> **NOTE:** The namespace for rule names on this resource (defined by the +[`name`](#name) argument) is shared with all rules in the cluster - consider +this when naming your rules. + +## 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 rule's key within the cluster configuration. + +## Importing + +An existing rule can be [imported][docs-import] into this resource by supplying +both the path to the cluster, and the name the rule. If the name or cluster is +not found, or if the rule is of a different type, an error will be returned. An +example is below: + +[docs-import]: https://www.terraform.io/docs/import/index.html + +``` +terraform import vsphere_compute_cluster_vm_anti_affinity_rule.cluster_vm_anti_affinity_rule \ + '{"compute_cluster_path": "/dc1/host/cluster1", \ + "name": "terraform-test-cluster-vm-anti-affinity-rule"}' +``` diff --git a/website/vsphere.erb b/website/vsphere.erb index d9ee69755..42f4a68e6 100644 --- a/website/vsphere.erb +++ b/website/vsphere.erb @@ -73,6 +73,12 @@ > vsphere_compute_cluster_host_group + > + vsphere_compute_cluster_vm_affinity_rule + + > + vsphere_compute_cluster_vm_anti_affinity_rule + > vsphere_compute_cluster_vm_dependency_rule