Files
vctp2/internal/vcenter/vcenter.go
Nathan Coad bc84931c37
All checks were successful
continuous-integration/drone/push Build is passing
Add vCenter cache rebuild functionality and related API endpoint
2026-02-16 08:46:38 +11:00

913 lines
25 KiB
Go

package vcenter
import (
"context"
"fmt"
"log/slog"
"net/url"
"path"
"strings"
"time"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/event"
"github.com/vmware/govmomi/fault"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/view"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
type Vcenter struct {
Logger *slog.Logger
Vurl string
ctx context.Context
client *govmomi.Client
credentials *VcenterLogin
}
type VcenterLogin struct {
Username string
Password string
Insecure bool
}
type VmProperties struct {
Vm mo.VirtualMachine
ResourcePool string
}
var clientUserAgent = "vCTP"
const (
defaultPropertyPageSize int32 = 500
)
func normalizeKinds(kind []string) []string {
if len(kind) > 0 {
return kind
}
return []string{"ManagedEntity"}
}
func normalizePageSize(pageSize int32) int32 {
if pageSize <= 0 {
return defaultPropertyPageSize
}
return pageSize
}
func buildPropertySpecs(kind []string, props []string) []types.PropertySpec {
kinds := normalizeKinds(kind)
specs := make([]types.PropertySpec, 0, len(kinds))
for _, t := range kinds {
spec := types.PropertySpec{Type: t}
if len(props) == 0 {
spec.All = types.NewBool(true)
} else {
spec.PathSet = props
}
specs = append(specs, spec)
}
return specs
}
func refKey(ref types.ManagedObjectReference) string {
return ref.Type + ":" + ref.Value
}
func (v *Vcenter) retrieveContainerViewPaged(ctx context.Context, cv *view.ContainerView, kind []string, props []string, dst any, pageSize int32) error {
pc := property.DefaultCollector(v.client.Client)
req := types.RetrieveProperties{
SpecSet: []types.PropertyFilterSpec{
{
ObjectSet: []types.ObjectSpec{
{
Obj: cv.Reference(),
Skip: types.NewBool(true),
SelectSet: []types.BaseSelectionSpec{
&types.TraversalSpec{
Type: cv.Reference().Type,
Path: "view",
},
},
},
},
PropSet: buildPropertySpecs(kind, props),
},
},
}
res, err := pc.RetrieveProperties(ctx, req, normalizePageSize(pageSize))
if err != nil {
return err
}
if d, ok := dst.(*[]types.ObjectContent); ok {
*d = res.Returnval
return nil
}
return mo.LoadObjectContent(res.Returnval, dst)
}
func (v *Vcenter) retrieveByRefsPaged(ctx context.Context, refs []types.ManagedObjectReference, props []string, dst any, pageSize int32) error {
if len(refs) == 0 {
return nil
}
kindSet := make(map[string]struct{}, len(refs))
kinds := make([]string, 0, len(refs))
for _, ref := range refs {
if _, ok := kindSet[ref.Type]; ok {
continue
}
kindSet[ref.Type] = struct{}{}
kinds = append(kinds, ref.Type)
}
objectSet := make([]types.ObjectSpec, 0, len(refs))
for _, ref := range refs {
objectSet = append(objectSet, types.ObjectSpec{
Obj: ref,
Skip: types.NewBool(false),
})
}
pc := property.DefaultCollector(v.client.Client)
req := types.RetrieveProperties{
SpecSet: []types.PropertyFilterSpec{
{
ObjectSet: objectSet,
PropSet: buildPropertySpecs(kinds, props),
},
},
}
res, err := pc.RetrieveProperties(ctx, req, normalizePageSize(pageSize))
if err != nil {
return err
}
if d, ok := dst.(*[]types.ObjectContent); ok {
*d = res.Returnval
return nil
}
return mo.LoadObjectContent(res.Returnval, dst)
}
// SetUserAgent customizes the User-Agent used when talking to vCenter.
func SetUserAgent(ua string) {
if strings.TrimSpace(ua) != "" {
clientUserAgent = ua
}
}
type HostLookup struct {
Cluster string
Datacenter string
}
type FolderLookup map[string]string
// New creates a new Vcenter with the given logger
func New(logger *slog.Logger, creds *VcenterLogin) *Vcenter {
//ctx, cancel := context.WithCancel(context.Background())
//defer cancel()
return &Vcenter{
Logger: logger,
ctx: context.Background(),
credentials: creds,
}
}
func (v *Vcenter) Login(vUrl string) error {
if v == nil {
return fmt.Errorf("vcenter is nil")
}
if strings.TrimSpace(vUrl) == "" {
return fmt.Errorf("vcenter URL is empty")
}
if v.credentials == nil {
return fmt.Errorf("vcenter credentials are nil")
}
// Connect to vCenter
u, err := soap.ParseURL(vUrl)
if err != nil {
return fmt.Errorf("error parsing vCenter URL: %w", err)
}
v.Vurl = vUrl
u.User = url.UserPassword(v.credentials.Username, v.credentials.Password)
/*
c, err := govmomi.NewClient(ctx, u, insecure)
if err != nil {
log.Fatalf("Error connecting to vCenter: %s", err)
}
*/
c, err := govmomi.NewClient(v.ctx, u, v.credentials.Insecure)
if err != nil {
v.Logger.Error("Unable to connect to vCenter", "error", err)
return fmt.Errorf("unable to connect to vCenter : %s", err)
}
if clientUserAgent != "" {
c.Client.UserAgent = clientUserAgent
}
//defer c.Logout(v.ctx)
v.client = c
v.Logger.Debug("successfully connected to vCenter", "url", vUrl, "username", v.credentials.Username)
return nil
}
func (v *Vcenter) Logout(ctx context.Context) error {
if ctx == nil {
ctx = v.ctx
}
if ctx == nil {
v.Logger.Warn("Nil context, unable to logout")
return nil
}
if v.client.Valid() {
return v.client.Logout(ctx)
}
v.Logger.Debug("vcenter client is not valid")
return nil
}
func (v *Vcenter) GetAllVmReferences() ([]*object.VirtualMachine, error) {
m := view.NewManager(v.client.Client)
cv, err := m.CreateContainerView(v.ctx, v.client.ServiceContent.RootFolder, []string{"VirtualMachine"}, true)
if err != nil {
return nil, fmt.Errorf("failed to create VM container view: %w", err)
}
defer cv.Destroy(v.ctx)
var content []types.ObjectContent
if err := v.retrieveContainerViewPaged(v.ctx, cv, []string{"VirtualMachine"}, []string{"name"}, &content, defaultPropertyPageSize); err != nil {
return nil, fmt.Errorf("failed to retrieve VM references: %w", err)
}
results := make([]*object.VirtualMachine, 0, len(content))
for _, c := range content {
results = append(results, object.NewVirtualMachine(v.client.Client, c.Obj))
}
v.Logger.Debug("Found VM references", "count", len(results))
return results, nil
}
// GetAllVMsWithProps returns all VMs with the properties needed for snapshotting in a single property-collector call.
func (v *Vcenter) GetAllVMsWithProps() ([]mo.VirtualMachine, error) {
m := view.NewManager(v.client.Client)
cv, err := m.CreateContainerView(v.ctx, v.client.ServiceContent.RootFolder, []string{"VirtualMachine"}, true)
if err != nil {
return nil, fmt.Errorf("failed to create VM container view: %w", err)
}
defer cv.Destroy(v.ctx)
var vms []mo.VirtualMachine
props := []string{
"name",
"parent",
"config.uuid",
"config.createDate",
"config.hardware",
"config.managedBy",
"config.template",
"summary.storage",
"runtime.powerState",
"runtime.host",
"resourcePool",
}
if err := v.retrieveContainerViewPaged(v.ctx, cv, []string{"VirtualMachine"}, props, &vms, defaultPropertyPageSize); err != nil {
return nil, fmt.Errorf("failed to retrieve VMs: %w", err)
}
return vms, nil
}
// GetVMWithSnapshotPropsByRef retrieves snapshot-critical properties for a single VM reference.
// Used as a targeted fallback when bulk retrieval returns partial data for a VM.
func (v *Vcenter) GetVMWithSnapshotPropsByRef(ref types.ManagedObjectReference) (*mo.VirtualMachine, error) {
var vm mo.VirtualMachine
props := []string{
"name",
"parent",
"config",
"summary.storage",
"runtime.powerState",
"runtime.host",
"resourcePool",
}
if err := v.client.RetrieveOne(v.ctx, ref, props, &vm); err != nil {
return nil, err
}
return &vm, nil
}
// FindVmDeletionEvents returns a map of MoRef (VmId) to the deletion event time within the given window.
func (v *Vcenter) FindVmDeletionEvents(ctx context.Context, begin, end time.Time) (map[string]time.Time, error) {
return v.findVmDeletionEvents(ctx, begin, end, nil)
}
// FindVmDeletionEventsForCandidates returns deletion event times for the provided VM IDs only.
func (v *Vcenter) FindVmDeletionEventsForCandidates(ctx context.Context, begin, end time.Time, candidates []string) (map[string]time.Time, error) {
if len(candidates) == 0 {
return map[string]time.Time{}, nil
}
candidateSet := make(map[string]struct{}, len(candidates))
for _, id := range candidates {
if id == "" {
continue
}
candidateSet[id] = struct{}{}
}
if len(candidateSet) == 0 {
return map[string]time.Time{}, nil
}
return v.findVmDeletionEvents(ctx, begin, end, candidateSet)
}
func (v *Vcenter) findVmDeletionEvents(ctx context.Context, begin, end time.Time, candidateSet map[string]struct{}) (map[string]time.Time, error) {
result := make(map[string]time.Time)
if v.client == nil || !v.client.Valid() {
return result, fmt.Errorf("vcenter client is not valid")
}
// vCenter events are stored in UTC; normalize the query window.
beginUTC := begin.UTC()
endUTC := end.UTC()
mgr := event.NewManager(v.client.Client)
type deletionHit struct {
ts time.Time
priority int
}
const (
deletionPriorityRemoved = iota
deletionPriorityVmEvent
deletionPriorityTask
)
hits := make(map[string]deletionHit)
foundCandidates := 0
recordDeletion := func(vmID string, ts time.Time, priority int) {
if vmID == "" {
return
}
if candidateSet != nil {
if _, ok := candidateSet[vmID]; !ok {
return
}
}
if prev, ok := hits[vmID]; !ok {
hits[vmID] = deletionHit{ts: ts, priority: priority}
if candidateSet != nil {
foundCandidates++
}
} else if priority < prev.priority || (priority == prev.priority && ts.Before(prev.ts)) {
hits[vmID] = deletionHit{ts: ts, priority: priority}
}
}
isDeletionMessage := func(msg string) bool {
msg = strings.ToLower(msg)
return strings.Contains(msg, "destroy") ||
strings.Contains(msg, "deleted") ||
strings.Contains(msg, "unregister") ||
strings.Contains(msg, "removed from inventory")
}
isVmDeletionTask := func(info types.TaskInfo, msg string) bool {
id := strings.ToLower(strings.TrimSpace(info.DescriptionId))
if id != "" {
if strings.Contains(id, "virtualmachine") &&
(strings.Contains(id, "destroy") || strings.Contains(id, "delete") || strings.Contains(id, "unregister")) {
return true
}
}
name := strings.ToLower(strings.TrimSpace(info.Name))
if name != "" {
if (strings.Contains(name, "destroy") || strings.Contains(name, "delete") || strings.Contains(name, "unregister")) &&
(strings.Contains(name, "virtualmachine") || strings.Contains(name, "virtual machine")) {
return true
}
}
if msg != "" && isDeletionMessage(msg) {
return true
}
return false
}
processEvents := func(evts []types.BaseEvent) {
for _, ev := range evts {
switch e := ev.(type) {
case *types.VmRemovedEvent:
if e.Vm != nil {
vmID := e.Vm.Vm.Value
recordDeletion(vmID, e.CreatedTime, deletionPriorityRemoved)
}
case *types.TaskEvent:
// Fallback for destroy task events.
if e.Info.Entity != nil {
vmID := e.Info.Entity.Value
if vmID != "" && isVmDeletionTask(e.Info, e.GetEvent().FullFormattedMessage) {
recordDeletion(vmID, e.CreatedTime, deletionPriorityTask)
}
}
case *types.VmEvent:
if e.Vm != nil {
vmID := e.Vm.Vm.Value
if vmID != "" && isDeletionMessage(e.GetEvent().FullFormattedMessage) {
recordDeletion(vmID, e.CreatedTime, deletionPriorityVmEvent)
}
}
}
}
}
const (
eventPageSize = int32(1000)
maxEventPages = 25
)
readCollector := func(label string, collector *event.HistoryCollector) error {
pageCount := 0
for {
events, err := collector.ReadNextEvents(ctx, eventPageSize)
if err != nil {
return err
}
if len(events) == 0 {
break
}
pageCount++
processEvents(events)
if candidateSet != nil && foundCandidates >= len(candidateSet) {
break
}
if pageCount >= maxEventPages {
if v.Logger != nil {
v.Logger.Warn("vcenter deletion events truncated", "vcenter", v.Vurl, "label", label, "pages", pageCount, "page_size", eventPageSize, "window_start_utc", beginUTC, "window_end_utc", endUTC)
}
break
}
}
return nil
}
// First attempt: specific deletion event types.
disableFullMessage := false
filter := types.EventFilterSpec{
Time: &types.EventFilterSpecByTime{
BeginTime: &beginUTC,
EndTime: &endUTC,
},
MaxCount: eventPageSize,
DisableFullMessage: &disableFullMessage,
EventTypeId: []string{
"VmRemovedEvent",
"TaskEvent",
},
}
collector, err := mgr.CreateCollectorForEvents(ctx, filter)
if err != nil {
return result, fmt.Errorf("failed to create event collector: %w", err)
}
defer collector.Destroy(ctx)
if err := readCollector("primary", collector); err != nil {
return result, fmt.Errorf("failed to read events: %w", err)
}
// If nothing found, widen the filter to all event types in the window as a fallback.
if len(hits) == 0 {
fallbackFilter := types.EventFilterSpec{
Time: &types.EventFilterSpecByTime{
BeginTime: &beginUTC,
EndTime: &endUTC,
},
MaxCount: eventPageSize,
DisableFullMessage: &disableFullMessage,
}
fc, err := mgr.CreateCollectorForEvents(ctx, fallbackFilter)
if err == nil {
defer fc.Destroy(ctx)
if readErr := readCollector("fallback", fc); readErr != nil && v.Logger != nil {
v.Logger.Warn("vcenter fallback event read failed", "vcenter", v.Vurl, "error", readErr)
}
}
}
for vmID, hit := range hits {
result[vmID] = hit.ts
}
return result, nil
}
func (v *Vcenter) BuildHostLookup() (map[string]HostLookup, error) {
finder := find.NewFinder(v.client.Client, true)
datacenters, err := finder.DatacenterList(v.ctx, "*")
if err != nil {
return nil, fmt.Errorf("failed to list datacenters: %w", err)
}
lookup := make(map[string]HostLookup)
clusterCache := make(map[string]string)
for _, dc := range datacenters {
finder.SetDatacenter(dc)
hosts, err := finder.HostSystemList(v.ctx, "*")
if err != nil {
v.Logger.Warn("failed to list hosts for datacenter", "datacenter", dc.Name(), "error", err)
continue
}
if len(hosts) == 0 {
continue
}
hostRefs := make([]types.ManagedObjectReference, 0, len(hosts))
for _, host := range hosts {
hostRefs = append(hostRefs, host.Reference())
}
var moHosts []mo.HostSystem
if err := v.retrieveByRefsPaged(v.ctx, hostRefs, []string{"parent"}, &moHosts, defaultPropertyPageSize); err != nil {
v.Logger.Warn("failed to retrieve host parents for datacenter", "datacenter", dc.Name(), "error", err)
continue
}
parentRefs := make([]types.ManagedObjectReference, 0, len(moHosts))
parentSeen := make(map[string]struct{})
for _, host := range moHosts {
if host.Parent == nil {
continue
}
key := refKey(*host.Parent)
if _, cached := clusterCache[key]; cached {
continue
}
if _, seen := parentSeen[key]; seen {
continue
}
parentSeen[key] = struct{}{}
parentRefs = append(parentRefs, *host.Parent)
}
if len(parentRefs) > 0 {
var computes []mo.ComputeResource
if err := v.retrieveByRefsPaged(v.ctx, parentRefs, []string{"name"}, &computes, defaultPropertyPageSize); err != nil {
v.Logger.Warn("failed to retrieve cluster names for datacenter", "datacenter", dc.Name(), "error", err)
} else {
for _, compute := range computes {
clusterCache[refKey(compute.Reference())] = compute.Name
}
}
}
for _, host := range moHosts {
clusterName := ""
if host.Parent != nil {
clusterName = clusterCache[refKey(*host.Parent)]
}
lookup[host.Reference().Value] = HostLookup{
Cluster: clusterName,
Datacenter: dc.Name(),
}
}
}
return lookup, nil
}
func (v *Vcenter) BuildFolderPathLookup() (FolderLookup, error) {
m := view.NewManager(v.client.Client)
folders, err := m.CreateContainerView(v.ctx, v.client.ServiceContent.RootFolder, []string{"Folder"}, true)
if err != nil {
return nil, err
}
defer folders.Destroy(v.ctx)
var results []mo.Folder
if err := v.retrieveContainerViewPaged(v.ctx, folders, []string{"Folder"}, []string{"name", "parent"}, &results, defaultPropertyPageSize); err != nil {
return nil, err
}
nameByID := make(map[string]string, len(results))
parentByID := make(map[string]*types.ManagedObjectReference, len(results))
for _, folder := range results {
nameByID[folder.Reference().Value] = folder.Name
parentByID[folder.Reference().Value] = folder.Parent
}
paths := make(FolderLookup, len(results))
var buildPath func(id string) string
buildPath = func(id string) string {
if pathValue, ok := paths[id]; ok {
return pathValue
}
name, ok := nameByID[id]
if !ok {
return ""
}
parent := parentByID[id]
if parent == nil || parent.Type == "Datacenter" {
paths[id] = path.Join("/", name)
return paths[id]
}
if parent.Type != "Folder" {
paths[id] = path.Join("/", name)
return paths[id]
}
parentPath := buildPath(parent.Value)
if parentPath == "" {
paths[id] = path.Join("/", name)
return paths[id]
}
paths[id] = path.Join(parentPath, name)
return paths[id]
}
for id := range nameByID {
_ = buildPath(id)
}
return paths, nil
}
func (v *Vcenter) GetVMFolderPathFromLookup(vm mo.VirtualMachine, lookup FolderLookup) (string, bool) {
if vm.Parent == nil || lookup == nil {
return "", false
}
pathValue, ok := lookup[vm.Parent.Value]
return pathValue, ok
}
func (v *Vcenter) ConvertObjToMoVM(vmObj *object.VirtualMachine) (*mo.VirtualMachine, error) {
if vmObj == nil {
return nil, fmt.Errorf("vm object is nil")
}
vmRef := vmObj.Reference()
var moVM mo.VirtualMachine
err := v.client.RetrieveOne(v.ctx, vmRef, []string{"name", "config"}, &moVM)
if err != nil {
return nil, fmt.Errorf("failed to retrieve VM %s: %w", vmObj.Name(), err)
}
return &moVM, nil
}
func (v *Vcenter) ConvertObjToMoHost(hostObj *object.HostSystem) (*mo.HostSystem, error) {
if hostObj == nil {
return nil, fmt.Errorf("host object is nil")
}
hostRef := hostObj.Reference()
var moHost mo.HostSystem
err := v.client.RetrieveOne(v.ctx, hostRef, []string{"name", "parent"}, &moHost)
if err != nil {
return nil, fmt.Errorf("failed to retrieve host %s: %w", hostObj.Name(), err)
}
v.Logger.Debug("found host", "host_name", moHost.Name)
return &moHost, nil
}
func (v *Vcenter) GetHostSystemObject(hostRef types.ManagedObjectReference) (*mo.HostSystem, error) {
var hs mo.HostSystem
if err := v.client.RetrieveOne(v.ctx, hostRef, []string{"name", "parent"}, &hs); err != nil {
return nil, err
}
v.Logger.Debug("found hostsystem", "name", hs.Name)
return &hs, nil
}
func (v *Vcenter) GetHostLookupByRef(hostRef types.ManagedObjectReference) (HostLookup, error) {
lookup := HostLookup{}
var host mo.HostSystem
if err := v.client.RetrieveOne(v.ctx, hostRef, []string{"parent"}, &host); err != nil {
return lookup, fmt.Errorf("failed to retrieve host parent: %w", err)
}
if host.Parent != nil && (host.Parent.Type == "ClusterComputeResource" || host.Parent.Type == "ComputeResource") {
var moCompute mo.ComputeResource
if err := v.client.RetrieveOne(v.ctx, *host.Parent, []string{"name"}, &moCompute); err != nil {
if v.Logger != nil {
v.Logger.Warn("failed to resolve cluster/compute name from host parent", "host_ref", hostRef.Value, "error", err)
}
} else {
lookup.Cluster = moCompute.Name
}
}
entities, err := mo.Ancestors(v.ctx, v.client.Client, v.client.ServiceContent.PropertyCollector, hostRef)
if err != nil {
if v.Logger != nil {
v.Logger.Warn("failed to resolve datacenter from host ancestors", "host_ref", hostRef.Value, "error", err)
}
return lookup, nil
}
for _, entity := range entities {
if entity.Self.Type != "Datacenter" {
continue
}
lookup.Datacenter = entity.Name
break
}
return lookup, nil
}
// Function to find the cluster or compute resource from a host reference
func (v *Vcenter) GetClusterFromHost(hostRef *types.ManagedObjectReference) (string, error) {
if hostRef == nil {
v.Logger.Warn("nil hostRef passed to GetClusterFromHost")
return "", nil
}
lookup, err := v.GetHostLookupByRef(*hostRef)
if err != nil {
v.Logger.Error("cant get host lookup", "error", err)
return "", err
}
return lookup.Cluster, nil
}
// Function to determine the datacenter a VM belongs to
func (v *Vcenter) GetDatacenterForVM(vm mo.VirtualMachine) (string, error) {
entities, err := mo.Ancestors(v.ctx, v.client.Client, v.client.ServiceContent.PropertyCollector, vm.Reference())
if err != nil {
return "", fmt.Errorf("failed to retrieve VM ancestors: %w", err)
}
for _, entity := range entities {
if entity.Self.Type != "Datacenter" {
continue
}
if entity.Name == "" {
return "", fmt.Errorf("datacenter ancestor found without name for VM %s", vm.Reference().Value)
}
return entity.Name, nil
}
return "", fmt.Errorf("failed to find datacenter for VM %s", vm.Reference().Value)
}
func (v *Vcenter) FindVMByName(vmName string) ([]mo.VirtualMachine, error) {
m := view.NewManager(v.client.Client)
vms, err := m.CreateContainerView(v.ctx, v.client.ServiceContent.RootFolder, []string{"VirtualMachine"}, true)
if err != nil {
return nil, err
}
defer vms.Destroy(v.ctx)
var matchingVMs []mo.VirtualMachine
err = v.retrieveContainerViewPaged(v.ctx, vms, []string{"VirtualMachine"}, []string{"name"}, &matchingVMs, defaultPropertyPageSize)
if err != nil {
return nil, err
}
// Temporarily just return all VMs
//return matchingVMs, nil
var result []mo.VirtualMachine
for _, vm := range matchingVMs {
if vm.Name == vmName {
result = append(result, vm)
}
}
return result, nil
}
func (v *Vcenter) FindVMByID(vmID string) (*VmProperties, error) {
v.Logger.Debug("searching for vm id", "vm_id", vmID)
vmRef := types.ManagedObjectReference{
Type: "VirtualMachine",
Value: vmID,
}
var vm mo.VirtualMachine
if err := v.client.RetrieveOne(v.ctx, vmRef, []string{"config", "name"}, &vm); err != nil {
return nil, fmt.Errorf("failed to retrieve VM %s: %w", vmID, err)
}
return &VmProperties{Vm: vm}, nil
}
func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*mo.VirtualMachine, error) {
if dcID != "" {
v.Logger.Debug("searching for vm id in datacenter context", "vm_id", vmID, "datacenter_id", dcID)
}
vmRef := types.ManagedObjectReference{
Type: "VirtualMachine",
Value: vmID,
}
var vm mo.VirtualMachine
props := []string{"name", "config", "runtime.powerState", "runtime.host", "resourcePool", "parent"}
if err := v.client.RetrieveOne(v.ctx, vmRef, props, &vm); err != nil {
if fault.Is(err, &types.ManagedObjectNotFound{}) {
return nil, nil
}
return nil, fmt.Errorf("failed to retrieve VM %s: %w", vmID, err)
}
return &vm, nil
}
func (v *Vcenter) GetResourcePoolNameByRef(resourcePoolRef types.ManagedObjectReference) (string, error) {
var pool mo.ResourcePool
if err := v.client.RetrieveOne(v.ctx, resourcePoolRef, []string{"name"}, &pool); err != nil {
return "", fmt.Errorf("failed to retrieve resource pool: %w", err)
}
return pool.Name, nil
}
// Helper function to retrieve the resource pool for the VM
func (v *Vcenter) GetVmResourcePool(vm mo.VirtualMachine) (string, error) {
var resourcePool string
if vm.ResourcePool != nil {
rpName, err := v.GetResourcePoolNameByRef(*vm.ResourcePool)
if err != nil {
v.Logger.Error("failed to get resource pool name", "error", err)
return resourcePool, err
} else {
//v.Logger.Debug("Found resource pool name", "rp_name", rpName)
resourcePool = rpName
}
}
return resourcePool, nil
}
// BuildResourcePoolLookup creates a cache of resource pool MoRef -> name for fast lookups.
func (v *Vcenter) BuildResourcePoolLookup() (map[string]string, error) {
m := view.NewManager(v.client.Client)
cv, err := m.CreateContainerView(v.ctx, v.client.ServiceContent.RootFolder, []string{"ResourcePool"}, true)
if err != nil {
return nil, fmt.Errorf("failed to create resource pool view: %w", err)
}
defer cv.Destroy(v.ctx)
var pools []mo.ResourcePool
if err := v.retrieveContainerViewPaged(v.ctx, cv, []string{"ResourcePool"}, []string{"name"}, &pools, defaultPropertyPageSize); err != nil {
return nil, fmt.Errorf("failed to retrieve resource pools: %w", err)
}
lookup := make(map[string]string, len(pools))
for _, pool := range pools {
lookup[pool.Reference().Value] = pool.Name
}
return lookup, nil
}
// Helper function to retrieve the full folder path for the VM
func (v *Vcenter) GetVMFolderPath(vm mo.VirtualMachine) (string, error) {
v.Logger.Debug("commencing vm folder path search", "vcenter", v.Vurl, "vm_id", vm.Reference().Value)
entities, err := mo.Ancestors(v.ctx, v.client.Client, v.client.ServiceContent.PropertyCollector, vm.Reference())
if err != nil {
return "", fmt.Errorf("failed to retrieve VM ancestors: %w", err)
}
var folderParts []string
seenDatacenter := false
vmRef := vm.Reference()
for _, entity := range entities {
switch entity.Self.Type {
case "Datacenter":
seenDatacenter = true
folderParts = folderParts[:0]
case "VirtualMachine":
if entity.Self == vmRef {
goto done
}
case "Folder":
if seenDatacenter {
folderParts = append(folderParts, entity.Name)
}
}
}
done:
if !seenDatacenter {
return "", fmt.Errorf("failed to find datacenter ancestor for VM %s", vm.Reference().Value)
}
if len(folderParts) == 0 {
return "", fmt.Errorf("failed to find folder path for VM %s", vm.Reference().Value)
}
return path.Join("/", path.Join(folderParts...)), nil
}