Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r/compute_cluster: New resource #487

Merged
merged 16 commits into from
Apr 26, 2018
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, of if it is not a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"...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