Skip to content

Commit

Permalink
Merge pull request #487 from terraform-providers/f-compute-cluster-re…
Browse files Browse the repository at this point in the history
…source

r/compute_cluster: New resource
  • Loading branch information
vancluever authored Apr 26, 2018
2 parents 95bf4e0 + ace5a9c commit 911b942
Show file tree
Hide file tree
Showing 14 changed files with 3,733 additions and 4 deletions.
4 changes: 4 additions & 0 deletions tf-vsphere-devrc.mk.example
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export VSPHERE_DC_FOLDER ?= dc-folder # DC resource test folder
export VSPHERE_ESXI_HOST ?= esxi1 # ESXi host to work with
export VSPHERE_ESXI_HOST2 ?= esxi2 # 2nd ESXi host to work with
export VSPHERE_ESXI_HOST3 ?= esxi3 # 3nd ESXi host to work with
export VSPHERE_ESXI_HOST4 ?= esxi4 # 4th ESXi host to work with
export VSPHERE_ESXI_HOST5 ?= esxi5 # 5th ESXi host to work with
export VSPHERE_ESXI_HOST6 ?= esxi6 # 6th ESXi host to work with
export VSPHERE_ESXI_HOST7 ?= esxi7 # 7th ESXi host to work with
export VSPHERE_HOST_NIC0 ?= vmnic0 # NIC0 for host net tests
export VSPHERE_HOST_NIC1 ?= vmnic1 # NIC1 for host net tests
export VSPHERE_VMFS_EXPECTED ?= scsi-name # Name of expected SCSI disk
Expand Down
22 changes: 22 additions & 0 deletions vsphere/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/clustercomputeresource"
"github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/datastore"
"github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/dvportgroup"
"github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/folder"
Expand Down Expand Up @@ -737,3 +738,24 @@ func testGetDatastoreClusterSDRSVMConfig(s *terraform.State, resourceName string

return resourceVSphereStorageDrsVMOverrideFindEntry(pod, vm)
}

// testGetComputeCluster is a convenience method to fetch a compute cluster by
// resource name.
func testGetComputeCluster(s *terraform.State, resourceName string) (*object.ClusterComputeResource, error) {
vars, err := testClientVariablesForResource(s, fmt.Sprintf("%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.
func testGetComputeClusterProperties(s *terraform.State, resourceName string) (*mo.ClusterComputeResource, error) {
cluster, err := testGetComputeCluster(s, resourceName)
if err != nil {
return nil, err
}
return clustercomputeresource.Properties(cluster)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package clustercomputeresource

import (
"context"
"fmt"
"log"
"strings"

"github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/computeresource"
"github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/folder"
"github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/hostsystem"
"github.com/terraform-providers/terraform-provider-vsphere/vsphere/internal/helper/provider"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)

// FromID locates a cluster by its managed object reference ID.
func FromID(client *govmomi.Client, id string) (*object.ClusterComputeResource, error) {
log.Printf("[DEBUG] Locating compute cluster with ID %q", id)
finder := find.NewFinder(client.Client, false)

ref := types.ManagedObjectReference{
Type: "ClusterComputeResource",
Value: id,
}

ctx, cancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout)
defer cancel()
r, err := finder.ObjectReference(ctx, ref)
if err != nil {
return nil, err
}
cluster := r.(*object.ClusterComputeResource)
log.Printf("[DEBUG] Compute cluster with ID %q found (%s)", cluster.Reference().Value, cluster.InventoryPath)
return cluster, nil
}

// FromPath loads a ClusterComputeResource from its path. The datacenter is
// optional if the path is specific enough to not require it.
func FromPath(client *govmomi.Client, name string, dc *object.Datacenter) (*object.ClusterComputeResource, error) {
finder := find.NewFinder(client.Client, false)
if dc != nil {
log.Printf("[DEBUG] Attempting to locate compute cluster %q in datacenter %q", name, dc.InventoryPath)
finder.SetDatacenter(dc)
} else {
log.Printf("[DEBUG] Attempting to locate compute cluster at absolute path %q", name)
}

ctx, cancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout)
defer cancel()
return finder.ClusterComputeResource(ctx, name)
}

// Properties is a convenience method that wraps fetching the
// ClusterComputeResource MO from its higher-level object.
func Properties(cluster *object.ClusterComputeResource) (*mo.ClusterComputeResource, error) {
ctx, cancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout)
defer cancel()
var props mo.ClusterComputeResource
if err := cluster.Properties(ctx, cluster.Reference(), nil, &props); err != nil {
return nil, err
}
return &props, nil
}

// Create creates a ClusterComputeResource in a supplied folder. The resulting
// ClusterComputeResource is returned.
func Create(f *object.Folder, name string, spec types.ClusterConfigSpecEx) (*object.ClusterComputeResource, error) {
log.Printf("[DEBUG] Creating compute cluster %q", fmt.Sprintf("%s/%s", f.InventoryPath, name))
ctx, cancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout)
defer cancel()
cluster, err := f.CreateCluster(ctx, name, spec)
if err != nil {
return nil, err
}
return cluster, nil
}

// Rename renames a ClusterComputeResource.
func Rename(cluster *object.ClusterComputeResource, name string) error {
log.Printf("[DEBUG] Renaming compute cluster %q to %s", cluster.InventoryPath, name)
ctx, cancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout)
defer cancel()
task, err := cluster.Rename(ctx, name)
if err != nil {
return err
}
return task.Wait(ctx)
}

// MoveToFolder is a complex method that moves a ClusterComputeResource to a given relative
// compute folder path. "Relative" here means relative to a datacenter, which
// is discovered from the current ClusterComputeResource path.
func MoveToFolder(client *govmomi.Client, cluster *object.ClusterComputeResource, relative string) error {
f, err := folder.HostFolderFromObject(client, cluster, relative)
if err != nil {
return err
}
return folder.MoveObjectTo(cluster.Reference(), f)
}

// HasChildren checks to see if a compute cluster has any child items (hosts
// and virtual machines) and returns true if that is the case. This is useful
// when checking to see if a compute cluster is safe to delete - destroying a
// compute cluster in vSphere destroys *all* children if at all possible
// (including removing hosts and virtual machines), so extra verification is
// necessary to prevent accidental removal.
func HasChildren(cluster *object.ClusterComputeResource) (bool, error) {
return computeresource.HasChildren(cluster)
}

// Reconfigure reconfigures a cluster. This just gets dispatched to
// computeresource as both methods are the same.
func Reconfigure(cluster *object.ClusterComputeResource, spec *types.ClusterConfigSpecEx) error {
return computeresource.Reconfigure(cluster, spec)
}

// Delete destroys a ClusterComputeResource.
func Delete(cluster *object.ClusterComputeResource) error {
log.Printf("[DEBUG] Deleting compute cluster %q", cluster.InventoryPath)
ctx, cancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout)
defer cancel()
task, err := cluster.Destroy(ctx)
if err != nil {
return err
}
return task.Wait(ctx)
}

// IsMember checks to see if a host is a member of the compute cluster
// in question.
//
// This is a pretty basic operation that checks that the parent of the
// compute is the ClusterComputeResource.
func IsMember(cluster *object.ClusterComputeResource, host *object.HostSystem) (bool, error) {
hprops, err := hostsystem.Properties(host)
if err != nil {
return false, fmt.Errorf("error getting properties for cluster %q: %s", host.Name(), err)
}
if hprops.Parent == nil {
return false, nil
}
if *hprops.Parent != cluster.Reference() {
return false, nil
}
return true, nil
}

// MoveHostsInto moves all of the supplied hosts into the cluster. All virtual
// machines are moved to the cluster's root resource pool and any resource
// pools on the host itself are deleted.
func MoveHostsInto(cluster *object.ClusterComputeResource, hosts []*object.HostSystem) error {
var hsNames []string
var hsRefs []types.ManagedObjectReference
for _, hs := range hosts {
hsNames = append(hsNames, hs.Name())
hsRefs = append(hsRefs, hs.Reference())
}
log.Printf("[DEBUG] Adding hosts into cluster %q: %s", cluster.Name(), strings.Join(hsNames, ", "))

req := types.MoveInto_Task{
This: cluster.Reference(),
Host: hsRefs,
}

ctx, cancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout)
defer cancel()
resp, err := methods.MoveInto_Task(ctx, cluster.Client(), &req)
if err != nil {
return err
}

task := object.NewTask(cluster.Client(), resp.Returnval)
return task.Wait(ctx)
}

// MoveHostsOutOf moves a supplied list of hosts out of the specified cluster.
// The host is moved to the root host folder for the datacenter that the
// cluster is in.
//
// The host is placed into maintenance mode with evacuate flagged on, ensuring
// that as many VMs as possible are moved out of the host before removing it
// from the cluster. The effectiveness of this operation is dictated by the
// cluster's DRS settings, which also affects if this means that the task will
// block and require manual intervention. The supplied timeout is passed to the
// maintenance mode operations, and represents the timeout in seconds.
//
// Individual hosts are taken out of maintenance mode after its operation is
// complete.
func MoveHostsOutOf(cluster *object.ClusterComputeResource, hosts []*object.HostSystem, timeout int) error {
for _, host := range hosts {
if err := moveHostOutOf(cluster, host, timeout); err != nil {
return err
}
}
return nil
}

func moveHostOutOf(cluster *object.ClusterComputeResource, host *object.HostSystem, timeout int) error {
// Place the host into maintenance mode. This blocks until the host is ready.
if err := hostsystem.EnterMaintenanceMode(host, timeout, true); err != nil {
return fmt.Errorf("error putting host %q into maintenance mode: %s", host.Name(), err)
}

// Host should be ready to move out of the cluster now.
f, err := folder.HostFolderFromObject(&govmomi.Client{Client: cluster.Client()}, host, "/")
if err != nil {
return err
}
log.Printf("[DEBUG] Moving host %q out of cluster %q and to folder %q", host.Name(), cluster.Name(), f.InventoryPath)
if err := folder.MoveObjectTo(host.Reference(), f); err != nil {
return fmt.Errorf("error moving host %q out of cluster %q: %s", host.Name(), cluster.Name(), err)
}

// Move the host out of maintenance mode now that it's out of the cluster.
if err := hostsystem.ExitMaintenanceMode(host, timeout); err != nil {
return fmt.Errorf("error taking host %q out of maintenance mode: %s", host.Name(), err)
}

log.Printf("[DEBUG] Host %q moved out of cluster %q successfully", host.Name(), cluster.Name())
return nil
}
43 changes: 43 additions & 0 deletions vsphere/internal/helper/computeresource/compute_resource_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,46 @@ func EnvironmentBrowserFromReference(client *govmomi.Client, ref types.ManagedOb
}
return envbrowse.NewEnvironmentBrowser(client.Client, *props.EnvironmentBrowser), nil
}

// Reconfigure reconfigures any BaseComputeResource that uses a
// BaseComputeResourceConfigSpec as configuration (example: standalone hosts,
// or clusters). Modify is always set.
func Reconfigure(obj BaseComputeResource, spec types.BaseComputeResourceConfigSpec) error {
var c *object.ComputeResource
switch t := obj.(type) {
case *object.ComputeResource:
log.Printf("[DEBUG] Reconfiguring standalone host %q", t.Name())
c = t
case *object.ClusterComputeResource:
log.Printf("[DEBUG] Reconfiguring cluster %q", t.Name())
c = &t.ComputeResource
default:
return fmt.Errorf("unsupported type for reconfigure: %T", t)
}

ctx, cancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout)
defer cancel()
task, err := c.Reconfigure(ctx, spec, true)
if err != nil {
return err
}
return task.Wait(ctx)
}

// HasChildren checks to see if a compute resource has any child items (hosts
// and virtual machines) and returns true if that is the case. This is useful
// when checking to see if a compute cluster is safe to delete - destroying a
// compute resource in vSphere destroys *all* children if at all possible
// (including removing hosts and virtual machines), so extra verification is
// necessary to prevent accidental removal.
func HasChildren(obj BaseComputeResource) (bool, error) {
props, err := BaseProperties(obj)
if err != nil {
return false, err
}

// We calculate if there is children based on host count alone as
// technically, if a compute resource has no hosts, it can't have virtual
// machines either.
return props.Summary.GetComputeResourceSummary().NumHosts > 0, nil
}
29 changes: 29 additions & 0 deletions vsphere/internal/helper/folder/folder_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ func folderFromObject(client *govmomi.Client, obj interface{}, folderType RootPa
p, err = RootPathParticleHost.PathFromNewRoot(o.InventoryPath, folderType, relative)
case *object.ResourcePool:
p, err = RootPathParticleHost.PathFromNewRoot(o.InventoryPath, folderType, relative)
case *object.ComputeResource:
p, err = RootPathParticleHost.PathFromNewRoot(o.InventoryPath, folderType, relative)
case *object.ClusterComputeResource:
p, err = RootPathParticleHost.PathFromNewRoot(o.InventoryPath, folderType, relative)
case *object.VirtualMachine:
p, err = RootPathParticleVM.PathFromNewRoot(o.InventoryPath, folderType, relative)
default:
Expand All @@ -220,6 +224,18 @@ func DatastoreFolderFromObject(client *govmomi.Client, obj interface{}, relative
return validateDatastoreFolder(folder)
}

// HostFolderFromObject returns an *object.Folder from a given object, and
// relative host folder path. If no such folder is found, or if it is not a
// host folder, an appropriate error will be returned.
func HostFolderFromObject(client *govmomi.Client, obj interface{}, relative string) (*object.Folder, error) {
folder, err := folderFromObject(client, obj, RootPathParticleHost, relative)
if err != nil {
return nil, err
}

return validateHostFolder(folder)
}

// VirtualMachineFolderFromObject returns an *object.Folder from a given
// object, and relative datastore folder path. If no such folder is found, or
// if it is not a VM folder, an appropriate error will be returned.
Expand Down Expand Up @@ -258,6 +274,19 @@ func validateDatastoreFolder(folder *object.Folder) (*object.Folder, error) {
return folder, nil
}

// validateHostFolder checks to make sure the folder is a host
// folder, and returns it if it is, or an error if it isn't.
func validateHostFolder(folder *object.Folder) (*object.Folder, error) {
ft, err := FindType(folder)
if err != nil {
return nil, err
}
if ft != VSphereFolderTypeHost {
return nil, fmt.Errorf("%q is not a host folder", folder.InventoryPath)
}
return folder, nil
}

// validateVirtualMachineFolder checks to make sure the folder is a VM folder,
// and returns it if it is, or an error if it isn't.
func validateVirtualMachineFolder(folder *object.Folder) (*object.Folder, error) {
Expand Down
Loading

0 comments on commit 911b942

Please sign in to comment.