diff --git a/internal/tasks/inventorySnapshots.go b/internal/tasks/inventorySnapshots.go index daf2f85..f81adeb 100644 --- a/internal/tasks/inventorySnapshots.go +++ b/internal/tasks/inventorySnapshots.go @@ -601,7 +601,7 @@ func normalizeResourcePool(value string) string { } } -func snapshotFromVM(vmObject *mo.VirtualMachine, vc *vcenter.Vcenter, snapshotTime time.Time, inv *queries.Inventory, hostLookup map[string]vcenter.HostLookup, folderLookup vcenter.FolderLookup) (inventorySnapshotRow, error) { +func snapshotFromVM(vmObject *mo.VirtualMachine, vc *vcenter.Vcenter, snapshotTime time.Time, inv *queries.Inventory, hostLookup map[string]vcenter.HostLookup, folderLookup vcenter.FolderLookup, rpLookup map[string]string) (inventorySnapshotRow, error) { if vmObject == nil { return inventorySnapshotRow{}, fmt.Errorf("missing VM object") } @@ -690,9 +690,16 @@ func snapshotFromVM(vmObject *mo.VirtualMachine, vc *vcenter.Vcenter, snapshotTi } } - if row.ResourcePool.String == "" { - if rpName, err := vc.GetVmResourcePool(*vmObject); err == nil { - row.ResourcePool = sql.NullString{String: normalizeResourcePool(rpName), Valid: rpName != ""} + if row.ResourcePool.String == "" && vmObject.ResourcePool != nil { + if rpLookup != nil { + if rpName, ok := rpLookup[vmObject.ResourcePool.Value]; ok { + row.ResourcePool = sql.NullString{String: normalizeResourcePool(rpName), Valid: rpName != ""} + } + } + if !row.ResourcePool.Valid { + if rpName, err := vc.GetVmResourcePool(*vmObject); err == nil { + row.ResourcePool = sql.NullString{String: normalizeResourcePool(rpName), Valid: rpName != ""} + } } } @@ -805,7 +812,7 @@ func (c *CronTask) captureHourlySnapshotForVcenter(ctx context.Context, startTim } }() - vcVms, err := vc.GetAllVmReferences() + vcVms, err := vc.GetAllVMsWithProps() if err != nil { return fmt.Errorf("unable to get VMs from vcenter: %w", err) } @@ -827,6 +834,13 @@ func (c *CronTask) captureHourlySnapshotForVcenter(ctx context.Context, startTim } else { c.Logger.Debug("built folder lookup", "url", url, "folders", len(folderLookup)) } + rpLookup, err := vc.BuildResourcePoolLookup() + if err != nil { + c.Logger.Warn("failed to build resource pool lookup", "url", url, "error", err) + rpLookup = nil + } else { + c.Logger.Debug("built resource pool lookup", "url", url, "pools", len(rpLookup)) + } inventoryRows, err := c.Database.Queries().GetInventoryByVcenter(ctx, url) if err != nil { @@ -844,16 +858,11 @@ func (c *CronTask) captureHourlySnapshotForVcenter(ctx context.Context, startTim presentSnapshots := make(map[string]inventorySnapshotRow, len(vcVms)) totals := snapshotTotals{} for _, vm := range vcVms { - if strings.HasPrefix(vm.Name(), "vCLS-") { + if strings.HasPrefix(vm.Name, "vCLS-") { continue } - vmObj, err := vc.ConvertObjToMoVM(vm) - if err != nil { - c.Logger.Error("failed to read VM details", "vm_id", vm.Reference().Value, "error", err) - continue - } - if vmObj.Config != nil && vmObj.Config.Template { + if vm.Config != nil && vm.Config.Template { continue } @@ -863,7 +872,7 @@ func (c *CronTask) captureHourlySnapshotForVcenter(ctx context.Context, startTim inv = &existingCopy } - row, err := snapshotFromVM(vmObj, vc, startTime, inv, hostLookup, folderLookup) + row, err := snapshotFromVM(&vm, vc, startTime, inv, hostLookup, folderLookup, rpLookup) if err != nil { c.Logger.Error("unable to build snapshot for VM", "vm_id", vm.Reference().Value, "error", err) continue diff --git a/internal/vcenter/vcenter.go b/internal/vcenter/vcenter.go index fe96468..4aaaeda 100644 --- a/internal/vcenter/vcenter.go +++ b/internal/vcenter/vcenter.go @@ -150,6 +150,34 @@ func (v *Vcenter) GetAllVmReferences() ([]*object.VirtualMachine, error) { return results, err } +// 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", + "runtime.powerState", + "runtime.host", + "resourcePool", + } + if err := cv.Retrieve(v.ctx, []string{"VirtualMachine"}, props, &vms); err != nil { + return nil, fmt.Errorf("failed to retrieve VMs: %w", err) + } + return vms, nil +} + func (v *Vcenter) BuildHostLookup() (map[string]HostLookup, error) { finder := find.NewFinder(v.client.Client, true) datacenters, err := finder.DatacenterList(v.ctx, "*") @@ -586,6 +614,27 @@ func (v *Vcenter) GetVmResourcePool(vm mo.VirtualMachine) (string, error) { 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 := cv.Retrieve(v.ctx, []string{"ResourcePool"}, []string{"name"}, &pools); 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) { //finder := find.NewFinder(v.client.Client, true)