telegraf/plugins/inputs/vsphere/finder.go

253 lines
6.8 KiB
Go

package vsphere
import (
"context"
"reflect"
"strings"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/view"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
var childTypes map[string][]string
var addFields map[string][]string
var containers map[string]interface{}
// Finder allows callers to find resources in vCenter given a query string.
type Finder struct {
client *Client
}
// ResourceFilter is a convenience class holding a finder and a set of paths. It is useful when you need a
// self contained object capable of returning a certain set of resources.
type ResourceFilter struct {
finder *Finder
resType string
paths []string
}
// FindAll returns the union of resources found given the supplied resource type and paths.
func (f *Finder) FindAll(ctx context.Context, resType string, paths []string, dst interface{}) error {
for _, p := range paths {
if err := f.Find(ctx, resType, p, dst); err != nil {
return err
}
}
return nil
}
// Find returns the resources matching the specified path.
func (f *Finder) Find(ctx context.Context, resType, path string, dst interface{}) error {
p := strings.Split(path, "/")
flt := make([]property.Filter, len(p)-1)
for i := 1; i < len(p); i++ {
flt[i-1] = property.Filter{"name": p[i]}
}
objs := make(map[string]types.ObjectContent)
err := f.descend(ctx, f.client.Client.ServiceContent.RootFolder, resType, flt, 0, objs)
if err != nil {
return err
}
objectContentToTypedArray(objs, dst)
f.client.log.Debugf("Find(%s, %s) returned %d objects", resType, path, len(objs))
return nil
}
func (f *Finder) descend(ctx context.Context, root types.ManagedObjectReference, resType string,
tokens []property.Filter, pos int, objs map[string]types.ObjectContent) error {
isLeaf := pos == len(tokens)-1
// No more tokens to match?
if pos >= len(tokens) {
return nil
}
// Determine child types
ct, ok := childTypes[root.Reference().Type]
if !ok {
// We don't know how to handle children of this type. Stop descending.
return nil
}
m := view.NewManager(f.client.Client.Client)
v, err := m.CreateContainerView(ctx, root, ct, false)
if err != nil {
return err
}
defer v.Destroy(ctx)
var content []types.ObjectContent
fields := []string{"name"}
recurse := tokens[pos]["name"] == "**"
types := ct
if isLeaf {
if af, ok := addFields[resType]; ok {
fields = append(fields, af...)
}
if recurse {
// Special case: The last token is a recursive wildcard, so we can grab everything
// recursively in a single call.
v2, err := m.CreateContainerView(ctx, root, []string{resType}, true)
defer v2.Destroy(ctx)
err = v2.Retrieve(ctx, []string{resType}, fields, &content)
if err != nil {
return err
}
for _, c := range content {
objs[c.Obj.String()] = c
}
return nil
}
types = []string{resType} // Only load wanted object type at leaf level
}
err = v.Retrieve(ctx, types, fields, &content)
if err != nil {
return err
}
rerunAsLeaf := false
for _, c := range content {
if !matchName(tokens[pos], c.PropSet) {
continue
}
// Already been here through another path? Skip!
if _, ok := objs[root.Reference().String()]; ok {
continue
}
if c.Obj.Type == resType && isLeaf {
// We found what we're looking for. Consider it a leaf and stop descending
objs[c.Obj.String()] = c
continue
}
// Deal with recursive wildcards (**)
var inc int
if recurse {
inc = 0 // By default, we stay on this token
if !isLeaf {
// Lookahead to next token.
if matchName(tokens[pos+1], c.PropSet) {
// Are we looking ahead at a leaf node that has the wanted type?
// Rerun the entire level as a leaf. This is needed since all properties aren't loaded
// when we're processing non-leaf nodes.
if pos == len(tokens)-2 {
if c.Obj.Type == resType {
rerunAsLeaf = true
continue
}
} else if _, ok := containers[c.Obj.Type]; ok {
// Tokens match and we're looking ahead at a container type that's not a leaf
// Consume this token and the next.
inc = 2
}
}
}
} else {
// The normal case: Advance to next token before descending
inc = 1
}
err := f.descend(ctx, c.Obj, resType, tokens, pos+inc, objs)
if err != nil {
return err
}
}
if rerunAsLeaf {
// We're at a "pseudo leaf", i.e. we looked ahead a token and found that this level contains leaf nodes.
// Rerun the entire level as a leaf to get those nodes. This will only be executed when pos is one token
// before the last, to pos+1 will always point to a leaf token.
return f.descend(ctx, root, resType, tokens, pos+1, objs)
}
return nil
}
func objectContentToTypedArray(objs map[string]types.ObjectContent, dst interface{}) error {
rt := reflect.TypeOf(dst)
if rt == nil || rt.Kind() != reflect.Ptr {
panic("need pointer")
}
rv := reflect.ValueOf(dst).Elem()
if !rv.CanSet() {
panic("cannot set dst")
}
for _, p := range objs {
v, err := mo.ObjectContentToType(p)
if err != nil {
return err
}
vt := reflect.TypeOf(v)
if !rv.Type().AssignableTo(vt) {
// For example: dst is []ManagedEntity, res is []HostSystem
if field, ok := vt.FieldByName(rt.Elem().Elem().Name()); ok && field.Anonymous {
rv.Set(reflect.Append(rv, reflect.ValueOf(v).FieldByIndex(field.Index)))
continue
}
}
rv.Set(reflect.Append(rv, reflect.ValueOf(v)))
}
return nil
}
// FindAll finds all resources matching the paths that were specified upon creation of
// the ResourceFilter.
func (r *ResourceFilter) FindAll(ctx context.Context, dst interface{}) error {
return r.finder.FindAll(ctx, r.resType, r.paths, dst)
}
func matchName(f property.Filter, props []types.DynamicProperty) bool {
for _, prop := range props {
if prop.Name == "name" {
return f.MatchProperty(prop)
}
}
return false
}
func init() {
childTypes = map[string][]string{
"HostSystem": {"VirtualMachine"},
"ComputeResource": {"HostSystem", "ResourcePool", "VirtualApp"},
"ClusterComputeResource": {"HostSystem", "ResourcePool", "VirtualApp"},
"Datacenter": {"Folder"},
"Folder": {
"Folder",
"Datacenter",
"VirtualMachine",
"ComputeResource",
"ClusterComputeResource",
"Datastore",
},
}
addFields = map[string][]string{
"HostSystem": {"parent", "summary.customValue", "customValue"},
"VirtualMachine": {"runtime.host", "config.guestId", "config.uuid", "runtime.powerState",
"summary.customValue", "guest.net", "guest.hostName", "customValue"},
"Datastore": {"parent", "info", "customValue"},
"ClusterComputeResource": {"parent", "customValue"},
"Datacenter": {"parent", "customValue"},
}
containers = map[string]interface{}{
"HostSystem": nil,
"ComputeResource": nil,
"Datacenter": nil,
"ResourcePool": nil,
"Folder": nil,
"VirtualApp": nil,
}
}