github.com/vmware/govmomi@v0.51.0/simulator/property_collector.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package simulator
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"log"
    11  	"path"
    12  	"reflect"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/google/uuid"
    18  
    19  	"github.com/vmware/govmomi/object"
    20  	"github.com/vmware/govmomi/simulator/internal"
    21  	"github.com/vmware/govmomi/vim25"
    22  	"github.com/vmware/govmomi/vim25/methods"
    23  	"github.com/vmware/govmomi/vim25/mo"
    24  	"github.com/vmware/govmomi/vim25/soap"
    25  	"github.com/vmware/govmomi/vim25/types"
    26  )
    27  
    28  type PropertyCollector struct {
    29  	mo.PropertyCollector
    30  
    31  	nopLocker
    32  	pending *types.UpdateSet
    33  	updates []types.ObjectUpdate
    34  	mu      sync.Mutex
    35  	cancel  context.CancelFunc
    36  }
    37  
    38  func NewPropertyCollector(ref types.ManagedObjectReference) object.Reference {
    39  	s := &PropertyCollector{}
    40  	s.Self = ref
    41  	return s
    42  }
    43  
    44  var errMissingField = errors.New("missing field")
    45  var errEmptyField = errors.New("empty field")
    46  var errInvalidField = errors.New("invalid field")
    47  
    48  func getObject(ctx *Context, ref types.ManagedObjectReference) (reflect.Value, bool) {
    49  	var obj mo.Reference
    50  	if ctx.Session == nil {
    51  		// Even without permissions to access an object or specific fields, RetrieveProperties
    52  		// returns an ObjectContent response as long as the object exists.  See retrieveResult.add()
    53  		obj = ctx.Map.Get(ref)
    54  	} else {
    55  		obj = ctx.Session.Get(ref)
    56  	}
    57  
    58  	if obj == nil {
    59  		return reflect.Value{}, false
    60  	}
    61  
    62  	if ctx.Session == nil && ref.Type == "SessionManager" {
    63  		// RetrieveProperties on SessionManager without a session always returns empty,
    64  		// rather than MissingSet + Fault.NotAuthenticated for each field.
    65  		obj = &mo.SessionManager{Self: ref}
    66  	}
    67  
    68  	// For objects that use internal types that differ from that of the vim25/mo field types.
    69  	// See EventHistoryCollector for example.
    70  	type get interface {
    71  		Get() mo.Reference
    72  	}
    73  	if o, ok := obj.(get); ok {
    74  		obj = o.Get()
    75  	}
    76  
    77  	return getManagedObject(obj), true
    78  }
    79  
    80  func getManagedObject(obj mo.Reference) reflect.Value {
    81  	rval := reflect.ValueOf(obj).Elem()
    82  	rtype := rval.Type()
    83  
    84  	// PropertyCollector is for Managed Object types only (package mo).
    85  	// If the registry object is not in the mo package, assume it is a wrapper
    86  	// type where the first field is an embedded mo type.
    87  	// We need to dig out the mo type for PropSet.All to work properly and
    88  	// for the case where the type has a field of the same name, for example:
    89  	// mo.ResourcePool.ResourcePool
    90  	for {
    91  		if path.Base(rtype.PkgPath()) == "mo" {
    92  			break
    93  		}
    94  		if rtype.Kind() != reflect.Struct || rtype.NumField() == 0 {
    95  			log.Panicf("%#v does not have an embedded mo type", obj.Reference())
    96  		}
    97  		rval = rval.Field(0)
    98  		rtype = rval.Type()
    99  		if rtype.Kind() == reflect.Pointer {
   100  			rval = rval.Elem()
   101  			rtype = rval.Type()
   102  		}
   103  	}
   104  
   105  	return rval
   106  }
   107  
   108  // wrapValue converts slice types to the appropriate ArrayOf type used in property collector responses.
   109  func wrapValue(rval reflect.Value, rtype reflect.Type) any {
   110  	pval := rval.Interface()
   111  
   112  	if rval.Kind() == reflect.Slice {
   113  		// Convert slice to types.ArrayOf*
   114  		switch v := pval.(type) {
   115  		case []string:
   116  			pval = &types.ArrayOfString{
   117  				String: v,
   118  			}
   119  		case []uint8:
   120  			pval = &types.ArrayOfByte{
   121  				Byte: v,
   122  			}
   123  		case types.ByteSlice:
   124  			pval = &types.ArrayOfByte{
   125  				Byte: v,
   126  			}
   127  		case []int16:
   128  			pval = &types.ArrayOfShort{
   129  				Short: v,
   130  			}
   131  		case []int32:
   132  			pval = &types.ArrayOfInt{
   133  				Int: v,
   134  			}
   135  		case []int64:
   136  			pval = &types.ArrayOfLong{
   137  				Long: v,
   138  			}
   139  		default:
   140  			kind := rtype.Elem().Name()
   141  			// Remove govmomi interface prefix name
   142  			kind = strings.TrimPrefix(kind, "Base")
   143  			akind, _ := defaultMapType("ArrayOf" + kind)
   144  			a := reflect.New(akind)
   145  			a.Elem().FieldByName(kind).Set(rval)
   146  			pval = a.Interface()
   147  		}
   148  	}
   149  
   150  	return pval
   151  }
   152  
   153  func fieldValueInterface(f reflect.StructField, rval reflect.Value, keyed ...bool) any {
   154  	if rval.Kind() == reflect.Ptr {
   155  		rval = rval.Elem()
   156  	}
   157  
   158  	if len(keyed) == 1 && keyed[0] {
   159  		return rval.Interface() // don't wrap keyed fields in ArrayOf* type
   160  	}
   161  
   162  	return wrapValue(rval, f.Type)
   163  }
   164  
   165  func fieldValue(rval reflect.Value, p string, keyed ...bool) (any, error) {
   166  	var value any
   167  	fields := strings.Split(p, ".")
   168  
   169  	for i, name := range fields {
   170  		kind := rval.Type().Kind()
   171  
   172  		if kind == reflect.Interface {
   173  			if rval.IsNil() {
   174  				continue
   175  			}
   176  			rval = rval.Elem()
   177  			kind = rval.Type().Kind()
   178  		}
   179  
   180  		if kind == reflect.Ptr {
   181  			if rval.IsNil() {
   182  				continue
   183  			}
   184  			rval = rval.Elem()
   185  		}
   186  
   187  		if kind == reflect.Slice {
   188  			// field of array field cannot be specified
   189  			return nil, errInvalidField
   190  		}
   191  
   192  		x := ucFirst(name)
   193  		val := rval.FieldByName(x)
   194  		if !val.IsValid() {
   195  			return nil, errMissingField
   196  		}
   197  
   198  		if isEmpty(val) {
   199  			return nil, errEmptyField
   200  		}
   201  
   202  		if i == len(fields)-1 {
   203  			ftype, _ := rval.Type().FieldByName(x)
   204  			value = fieldValueInterface(ftype, val, keyed...)
   205  			break
   206  		}
   207  
   208  		rval = val
   209  	}
   210  
   211  	return value, nil
   212  }
   213  
   214  func fieldValueKey(rval reflect.Value, p mo.Field) (any, error) {
   215  	if rval.Kind() != reflect.Slice {
   216  		return nil, errInvalidField
   217  	}
   218  
   219  	zero := reflect.Value{}
   220  
   221  	for i := 0; i < rval.Len(); i++ {
   222  		item := rval.Index(i)
   223  		if item.Kind() == reflect.Interface {
   224  			item = item.Elem()
   225  		}
   226  		if item.Kind() == reflect.Ptr {
   227  			item = item.Elem()
   228  		}
   229  		if item.Kind() != reflect.Struct {
   230  			return reflect.Value{}, errInvalidField
   231  		}
   232  
   233  		field := item.FieldByName("Key")
   234  		if field == zero {
   235  			return nil, errInvalidField
   236  		}
   237  
   238  		switch key := field.Interface().(type) {
   239  		case string:
   240  			s, ok := p.Key.(string)
   241  			if !ok {
   242  				return nil, errInvalidField
   243  			}
   244  			if s == key {
   245  				return item.Interface(), nil
   246  			}
   247  		case int32:
   248  			s, ok := p.Key.(int32)
   249  			if !ok {
   250  				return nil, errInvalidField
   251  			}
   252  			if s == key {
   253  				return item.Interface(), nil
   254  			}
   255  		default:
   256  			return nil, errInvalidField
   257  		}
   258  	}
   259  
   260  	return nil, nil
   261  }
   262  
   263  func fieldValueIndex(rval reflect.Value, p mo.Field) (any, error) {
   264  	val, err := fieldValueKey(rval, p)
   265  	if err != nil || val == nil || p.Item == "" {
   266  		return val, err
   267  	}
   268  
   269  	return fieldValue(reflect.ValueOf(val), p.Item)
   270  }
   271  
   272  func fieldRefs(f any) []types.ManagedObjectReference {
   273  	switch fv := f.(type) {
   274  	case types.ManagedObjectReference:
   275  		return []types.ManagedObjectReference{fv}
   276  	case *types.ArrayOfManagedObjectReference:
   277  		return fv.ManagedObjectReference
   278  	case nil:
   279  		// empty field
   280  	}
   281  
   282  	return nil
   283  }
   284  
   285  func isEmpty(rval reflect.Value) bool {
   286  	switch rval.Kind() {
   287  	case reflect.Ptr:
   288  		return rval.IsNil()
   289  	case reflect.String:
   290  		return rval.Len() == 0
   291  	}
   292  
   293  	return false
   294  }
   295  
   296  func isTrue(v *bool) bool {
   297  	return v != nil && *v
   298  }
   299  
   300  func isFalse(v *bool) bool {
   301  	return v == nil || !*v
   302  }
   303  
   304  func toString(v *string) string {
   305  	if v == nil {
   306  		return ""
   307  	}
   308  	return *v
   309  }
   310  
   311  func lcFirst(s string) string {
   312  	if len(s) < 1 {
   313  		return s
   314  	}
   315  	return strings.ToLower(s[:1]) + s[1:]
   316  }
   317  
   318  func ucFirst(s string) string {
   319  	if len(s) < 1 {
   320  		return s
   321  	}
   322  	return strings.ToUpper(s[:1]) + s[1:]
   323  }
   324  
   325  type retrieveResult struct {
   326  	*types.RetrieveResult
   327  	req       *types.RetrievePropertiesEx
   328  	collected map[types.ManagedObjectReference]bool
   329  	specs     map[string]*types.TraversalSpec
   330  }
   331  
   332  func (rr *retrieveResult) add(ctx *Context, name string, val types.AnyType, content *types.ObjectContent) {
   333  	if ctx.Session != nil {
   334  		content.PropSet = append(content.PropSet, types.DynamicProperty{
   335  			Name: name,
   336  			Val:  val,
   337  		})
   338  		return
   339  	}
   340  
   341  	content.MissingSet = append(content.MissingSet, types.MissingProperty{
   342  		Path: name,
   343  		Fault: types.LocalizedMethodFault{Fault: &types.NotAuthenticated{
   344  			NoPermission: types.NoPermission{
   345  				Object:      &content.Obj,
   346  				PrivilegeId: "System.Read",
   347  			}},
   348  		},
   349  	})
   350  }
   351  
   352  func (rr *retrieveResult) collectAll(ctx *Context, rval reflect.Value, rtype reflect.Type, content *types.ObjectContent) {
   353  	for i := 0; i < rval.NumField(); i++ {
   354  		val := rval.Field(i)
   355  
   356  		f := rtype.Field(i)
   357  
   358  		if isEmpty(val) || f.Name == "Self" {
   359  			continue
   360  		}
   361  
   362  		if f.Anonymous {
   363  			// recurse into embedded field
   364  			rr.collectAll(ctx, val, f.Type, content)
   365  			continue
   366  		}
   367  
   368  		rr.add(ctx, lcFirst(f.Name), fieldValueInterface(f, val), content)
   369  	}
   370  }
   371  
   372  func (rr *retrieveResult) collectFields(ctx *Context, rval reflect.Value, fields []string, content *types.ObjectContent) {
   373  	seen := make(map[string]bool)
   374  
   375  	for i := range content.PropSet {
   376  		seen[content.PropSet[i].Name] = true // mark any already collected via embedded field
   377  	}
   378  
   379  	for _, name := range fields {
   380  		if seen[name] {
   381  			// rvc 'ls' includes the "name" property twice, then fails with no error message or stack trace
   382  			// in RbVmomi::VIM::ObjectContent.to_hash_uncached when it sees the 2nd "name" property.
   383  			continue
   384  		}
   385  		seen[name] = true
   386  
   387  		var val any
   388  		var err error
   389  		var field mo.Field
   390  		if field.FromString(name) {
   391  			keyed := field.Key != nil
   392  
   393  			val, err = fieldValue(rval, field.Path, keyed)
   394  			if err == nil && keyed {
   395  				val, err = fieldValueIndex(reflect.ValueOf(val), field)
   396  			}
   397  		} else {
   398  			err = errInvalidField
   399  		}
   400  
   401  		switch err {
   402  		case nil, errEmptyField:
   403  			rr.add(ctx, name, val, content)
   404  		case errMissingField:
   405  			content.MissingSet = append(content.MissingSet, types.MissingProperty{
   406  				Path: name,
   407  				Fault: types.LocalizedMethodFault{Fault: &types.InvalidProperty{
   408  					Name: name,
   409  				}},
   410  			})
   411  		case errInvalidField:
   412  			content.MissingSet = append(content.MissingSet, types.MissingProperty{
   413  				Path: name,
   414  				Fault: types.LocalizedMethodFault{Fault: &types.InvalidProperty{
   415  					Name: name,
   416  				}},
   417  			})
   418  		}
   419  	}
   420  }
   421  
   422  func (rr *retrieveResult) collect(ctx *Context, ref types.ManagedObjectReference) {
   423  	if rr.collected[ref] {
   424  		return
   425  	}
   426  
   427  	content := types.ObjectContent{
   428  		Obj: ref,
   429  	}
   430  
   431  	rval, ok := getObject(ctx, ref)
   432  	if !ok {
   433  		// Possible if a test uses ctx.Map.Remove instead of Destroy_Task
   434  		tracef("object %s no longer exists", ref)
   435  		return
   436  	}
   437  
   438  	rtype := rval.Type()
   439  	match := false
   440  
   441  	for _, spec := range rr.req.SpecSet {
   442  		for _, p := range spec.PropSet {
   443  			if p.Type != ref.Type && p.Type != rtype.Name() {
   444  				// e.g. ManagedEntity, ComputeResource
   445  				field, ok := rtype.FieldByName(p.Type)
   446  
   447  				if !(ok && field.Anonymous) {
   448  					continue
   449  				}
   450  			}
   451  			match = true
   452  			if isTrue(p.All) {
   453  				rr.collectAll(ctx, rval, rtype, &content)
   454  				continue
   455  			}
   456  
   457  			rr.collectFields(ctx, rval, p.PathSet, &content)
   458  		}
   459  	}
   460  
   461  	if match {
   462  		// Copy while content.Obj is locked, as it won't be when final results are encoded.
   463  		var dst types.ObjectContent
   464  		deepCopy(&content, &dst)
   465  		rr.Objects = append(rr.Objects, dst)
   466  	}
   467  
   468  	rr.collected[ref] = true
   469  }
   470  
   471  func (rr *retrieveResult) selectSet(ctx *Context, obj reflect.Value, s []types.BaseSelectionSpec, refs *[]types.ManagedObjectReference) types.BaseMethodFault {
   472  	for _, ss := range s {
   473  		ts, ok := ss.(*types.TraversalSpec)
   474  		if ok {
   475  			if ts.Name != "" {
   476  				rr.specs[ts.Name] = ts
   477  			}
   478  		}
   479  	}
   480  
   481  	for _, ss := range s {
   482  		ts, ok := ss.(*types.TraversalSpec)
   483  		if !ok {
   484  			ts = rr.specs[ss.GetSelectionSpec().Name]
   485  			if ts == nil {
   486  				return &types.InvalidArgument{InvalidProperty: "undefined TraversalSpec name"}
   487  			}
   488  		}
   489  
   490  		f, _ := fieldValue(obj, ts.Path)
   491  
   492  		for _, ref := range fieldRefs(f) {
   493  			if isFalse(ts.Skip) {
   494  				*refs = append(*refs, ref)
   495  			}
   496  
   497  			rval, ok := getObject(ctx, ref)
   498  			if ok {
   499  				if err := rr.selectSet(ctx, rval, ts.SelectSet, refs); err != nil {
   500  					return err
   501  				}
   502  			}
   503  		}
   504  	}
   505  
   506  	return nil
   507  }
   508  
   509  func collect(ctx *Context, r *types.RetrievePropertiesEx) (*types.RetrieveResult, types.BaseMethodFault) {
   510  	var refs []types.ManagedObjectReference
   511  
   512  	rr := &retrieveResult{
   513  		RetrieveResult: &types.RetrieveResult{},
   514  		req:            r,
   515  		collected:      make(map[types.ManagedObjectReference]bool),
   516  		specs:          make(map[string]*types.TraversalSpec),
   517  	}
   518  
   519  	// Select object references
   520  	for _, spec := range r.SpecSet {
   521  		for _, o := range spec.ObjectSet {
   522  			unlock := ctx.Map.AcquireLock(ctx, o.Obj)
   523  			rval, ok := getObject(ctx, o.Obj)
   524  			if !ok {
   525  				unlock()
   526  				if isFalse(spec.ReportMissingObjectsInResults) {
   527  					return nil, &types.ManagedObjectNotFound{Obj: o.Obj}
   528  				}
   529  				continue
   530  			}
   531  
   532  			if o.SelectSet == nil || isFalse(o.Skip) {
   533  				rr.collect(ctx, o.Obj)
   534  			}
   535  
   536  			err := rr.selectSet(ctx, rval, o.SelectSet, &refs)
   537  			unlock()
   538  			if err != nil {
   539  				return nil, err
   540  			}
   541  		}
   542  	}
   543  
   544  	for _, ref := range refs {
   545  		ctx.WithLock(ref, func() { rr.collect(ctx, ref) })
   546  	}
   547  
   548  	return rr.RetrieveResult, nil
   549  }
   550  
   551  func (pc *PropertyCollector) CreateFilter(ctx *Context, c *types.CreateFilter) soap.HasFault {
   552  	body := &methods.CreateFilterBody{}
   553  
   554  	filter := &PropertyFilter{
   555  		pc:   pc,
   556  		refs: make(map[types.ManagedObjectReference]struct{}),
   557  	}
   558  	filter.PartialUpdates = c.PartialUpdates
   559  	filter.Spec = c.Spec
   560  
   561  	pc.Filter = append(pc.Filter, ctx.Session.Put(filter).Reference())
   562  
   563  	body.Res = &types.CreateFilterResponse{
   564  		Returnval: filter.Self,
   565  	}
   566  
   567  	ctx.Map.AddHandler(filter)
   568  
   569  	return body
   570  }
   571  
   572  func (pc *PropertyCollector) CreatePropertyCollector(ctx *Context, c *types.CreatePropertyCollector) soap.HasFault {
   573  	body := &methods.CreatePropertyCollectorBody{}
   574  
   575  	cpc := &PropertyCollector{}
   576  
   577  	body.Res = &types.CreatePropertyCollectorResponse{
   578  		Returnval: ctx.Session.Put(cpc).Reference(),
   579  	}
   580  
   581  	return body
   582  }
   583  
   584  func (pc *PropertyCollector) DestroyPropertyCollector(ctx *Context, c *types.DestroyPropertyCollector) soap.HasFault {
   585  	pc.CancelWaitForUpdates(&types.CancelWaitForUpdates{This: c.This})
   586  
   587  	body := &methods.DestroyPropertyCollectorBody{}
   588  
   589  	for _, ref := range pc.Filter {
   590  		// Same as DestroyPropertyFilter
   591  		ctx.Map.RemoveHandler(ctx.Session.Get(ref).(*PropertyFilter))
   592  		ctx.Session.Remove(ctx, ref)
   593  	}
   594  
   595  	ctx.Map.RemoveHandler(pc)
   596  	ctx.Session.Remove(ctx, c.This)
   597  
   598  	body.Res = &types.DestroyPropertyCollectorResponse{}
   599  
   600  	return body
   601  }
   602  
   603  var retrievePropertiesExBook sync.Map
   604  
   605  type retrievePropertiesExPage struct {
   606  	MaxObjects int32
   607  	Objects    []types.ObjectContent
   608  }
   609  
   610  func (pc *PropertyCollector) ContinueRetrievePropertiesEx(ctx *Context, r *types.ContinueRetrievePropertiesEx) soap.HasFault {
   611  	body := &methods.ContinueRetrievePropertiesExBody{}
   612  
   613  	if r.Token == "" {
   614  		body.Fault_ = Fault("", &types.InvalidPropertyFault{Name: "token"})
   615  		return body
   616  	}
   617  
   618  	obj, ok := retrievePropertiesExBook.LoadAndDelete(r.Token)
   619  	if !ok {
   620  		body.Fault_ = Fault("", &types.InvalidPropertyFault{Name: "token"})
   621  		return body
   622  	}
   623  
   624  	page := obj.(retrievePropertiesExPage)
   625  
   626  	var (
   627  		objsToStore  []types.ObjectContent
   628  		objsToReturn []types.ObjectContent
   629  	)
   630  	for i := range page.Objects {
   631  		if page.MaxObjects <= 0 || i < int(page.MaxObjects) {
   632  			objsToReturn = append(objsToReturn, page.Objects[i])
   633  		} else {
   634  			objsToStore = append(objsToStore, page.Objects[i])
   635  		}
   636  	}
   637  
   638  	if len(objsToStore) > 0 {
   639  		body.Res = &types.ContinueRetrievePropertiesExResponse{}
   640  		body.Res.Returnval.Token = uuid.NewString()
   641  		retrievePropertiesExBook.Store(
   642  			body.Res.Returnval.Token,
   643  			retrievePropertiesExPage{
   644  				MaxObjects: page.MaxObjects,
   645  				Objects:    objsToStore,
   646  			})
   647  	}
   648  
   649  	if len(objsToReturn) > 0 {
   650  		if body.Res == nil {
   651  			body.Res = &types.ContinueRetrievePropertiesExResponse{}
   652  		}
   653  		body.Res.Returnval.Objects = objsToReturn
   654  	}
   655  
   656  	return body
   657  }
   658  
   659  func (pc *PropertyCollector) RetrievePropertiesEx(ctx *Context, r *types.RetrievePropertiesEx) soap.HasFault {
   660  	body := &methods.RetrievePropertiesExBody{}
   661  
   662  	res, fault := collect(ctx, r)
   663  
   664  	if fault != nil {
   665  		switch fault.(type) {
   666  		case *types.ManagedObjectNotFound:
   667  			body.Fault_ = Fault("The object has already been deleted or has not been completely created", fault)
   668  		default:
   669  			body.Fault_ = Fault("", fault)
   670  		}
   671  	} else {
   672  		objects := res.Objects[:0]
   673  
   674  		var (
   675  			objsToStore  []types.ObjectContent
   676  			objsToReturn []types.ObjectContent
   677  		)
   678  		for i := range res.Objects {
   679  			if r.Options.MaxObjects <= 0 || i < int(r.Options.MaxObjects) {
   680  				objsToReturn = append(objsToReturn, res.Objects[i])
   681  			} else {
   682  				objsToStore = append(objsToStore, res.Objects[i])
   683  			}
   684  		}
   685  
   686  		if len(objsToStore) > 0 {
   687  			res.Token = uuid.NewString()
   688  			retrievePropertiesExBook.Store(res.Token, retrievePropertiesExPage{
   689  				MaxObjects: r.Options.MaxObjects,
   690  				Objects:    objsToStore,
   691  			})
   692  		}
   693  
   694  		for _, o := range objsToReturn {
   695  			propSet := o.PropSet[:0]
   696  			for _, p := range o.PropSet {
   697  				if p.Val != nil {
   698  					propSet = append(propSet, p)
   699  				}
   700  			}
   701  			o.PropSet = propSet
   702  
   703  			objects = append(objects, o)
   704  		}
   705  		res.Objects = objects
   706  		body.Res = &types.RetrievePropertiesExResponse{
   707  			Returnval: res,
   708  		}
   709  	}
   710  
   711  	return body
   712  }
   713  
   714  // RetrieveProperties is deprecated, but govmomi is still using it at the moment.
   715  func (pc *PropertyCollector) RetrieveProperties(ctx *Context, r *types.RetrieveProperties) soap.HasFault {
   716  	body := &methods.RetrievePropertiesBody{}
   717  
   718  	res := pc.RetrievePropertiesEx(ctx, &types.RetrievePropertiesEx{
   719  		This:    r.This,
   720  		SpecSet: r.SpecSet,
   721  	})
   722  
   723  	if res.Fault() != nil {
   724  		body.Fault_ = res.Fault()
   725  	} else {
   726  		body.Res = &types.RetrievePropertiesResponse{
   727  			Returnval: res.(*methods.RetrievePropertiesExBody).Res.Returnval.Objects,
   728  		}
   729  	}
   730  
   731  	return body
   732  }
   733  
   734  func (pc *PropertyCollector) CancelWaitForUpdates(r *types.CancelWaitForUpdates) soap.HasFault {
   735  	pc.mu.Lock()
   736  	if pc.cancel != nil {
   737  		pc.cancel()
   738  	}
   739  	pc.mu.Unlock()
   740  
   741  	return &methods.CancelWaitForUpdatesBody{Res: new(types.CancelWaitForUpdatesResponse)}
   742  }
   743  
   744  func (pc *PropertyCollector) update(u types.ObjectUpdate) {
   745  	pc.mu.Lock()
   746  	pc.updates = append(pc.updates, u)
   747  	pc.mu.Unlock()
   748  }
   749  
   750  func (pc *PropertyCollector) PutObject(_ *Context, o mo.Reference) {
   751  	pc.update(types.ObjectUpdate{
   752  		Obj:       o.Reference(),
   753  		Kind:      types.ObjectUpdateKindEnter,
   754  		ChangeSet: nil,
   755  	})
   756  }
   757  
   758  func (pc *PropertyCollector) UpdateObject(_ *Context, o mo.Reference, changes []types.PropertyChange) {
   759  	pc.update(types.ObjectUpdate{
   760  		Obj:       o.Reference(),
   761  		Kind:      types.ObjectUpdateKindModify,
   762  		ChangeSet: changes,
   763  	})
   764  }
   765  
   766  func (pc *PropertyCollector) RemoveObject(_ *Context, ref types.ManagedObjectReference) {
   767  	pc.update(types.ObjectUpdate{
   768  		Obj:       ref,
   769  		Kind:      types.ObjectUpdateKindLeave,
   770  		ChangeSet: nil,
   771  	})
   772  }
   773  
   774  func (pc *PropertyCollector) apply(ctx *Context, update *types.UpdateSet) types.BaseMethodFault {
   775  	for _, ref := range pc.Filter {
   776  		filter, ok := ctx.Session.Get(ref).(*PropertyFilter)
   777  		if !ok {
   778  			continue
   779  		}
   780  
   781  		res, fault := filter.collect(ctx)
   782  		if fault != nil {
   783  			return fault
   784  		}
   785  
   786  		fu := types.PropertyFilterUpdate{
   787  			Filter: ref,
   788  		}
   789  
   790  		for _, o := range res.Objects {
   791  			if _, ok := filter.refs[o.Obj]; ok {
   792  				continue
   793  			}
   794  			filter.refs[o.Obj] = struct{}{}
   795  			ou := types.ObjectUpdate{
   796  				Obj:  o.Obj,
   797  				Kind: types.ObjectUpdateKindEnter,
   798  			}
   799  
   800  			for _, p := range o.PropSet {
   801  				ou.ChangeSet = append(ou.ChangeSet, types.PropertyChange{
   802  					Op:   types.PropertyChangeOpAssign,
   803  					Name: p.Name,
   804  					Val:  p.Val,
   805  				})
   806  			}
   807  
   808  			fu.ObjectSet = append(fu.ObjectSet, ou)
   809  		}
   810  
   811  		if len(fu.ObjectSet) != 0 {
   812  			update.FilterSet = append(update.FilterSet, fu)
   813  		}
   814  	}
   815  	return nil
   816  }
   817  
   818  // pageUpdateSet limits the given UpdateSet to max number of object updates.
   819  // nil is returned when not truncated, otherwise the remaining UpdateSet.
   820  func pageUpdateSet(update *types.UpdateSet, max int) *types.UpdateSet {
   821  	for i := range update.FilterSet {
   822  		set := update.FilterSet[i].ObjectSet
   823  		n := len(set)
   824  		if n+1 > max {
   825  			update.Truncated = types.NewBool(true)
   826  			f := types.PropertyFilterUpdate{
   827  				Filter:    update.FilterSet[i].Filter,
   828  				ObjectSet: update.FilterSet[i].ObjectSet[max:],
   829  			}
   830  			update.FilterSet[i].ObjectSet = update.FilterSet[i].ObjectSet[:max]
   831  
   832  			pending := &types.UpdateSet{
   833  				Version:   "P",
   834  				FilterSet: []types.PropertyFilterUpdate{f},
   835  			}
   836  
   837  			if len(update.FilterSet) > i {
   838  				pending.FilterSet = append(pending.FilterSet, update.FilterSet[i+1:]...)
   839  				update.FilterSet = update.FilterSet[:i+1]
   840  			}
   841  
   842  			return pending
   843  		}
   844  		max -= n
   845  	}
   846  	return nil
   847  }
   848  
   849  // WaitOptions.maxObjectUpdates says:
   850  // > PropertyCollector policy may still limit the total count
   851  // > to something less than maxObjectUpdates.
   852  // Seems to be "may" == "will" and the default max is 100.
   853  const defaultMaxObjectUpdates = 100 // vCenter's default
   854  
   855  func (pc *PropertyCollector) WaitForUpdatesEx(ctx *Context, r *types.WaitForUpdatesEx) soap.HasFault {
   856  	wait, cancel := context.WithCancel(context.Background())
   857  	oneUpdate := false
   858  	maxObject := defaultMaxObjectUpdates
   859  	if r.Options != nil {
   860  		if max := r.Options.MaxWaitSeconds; max != nil {
   861  			// A value of 0 causes WaitForUpdatesEx to do one update calculation and return any results.
   862  			oneUpdate = (*max == 0)
   863  			if *max > 0 {
   864  				wait, cancel = context.WithTimeout(context.Background(), time.Second*time.Duration(*max))
   865  			}
   866  		}
   867  		if max := r.Options.MaxObjectUpdates; max > 0 && max < defaultMaxObjectUpdates {
   868  			maxObject = int(max)
   869  		}
   870  	}
   871  	pc.mu.Lock()
   872  	pc.cancel = cancel
   873  	pc.mu.Unlock()
   874  
   875  	body := &methods.WaitForUpdatesExBody{}
   876  
   877  	set := &types.UpdateSet{
   878  		Version: r.Version,
   879  	}
   880  
   881  	body.Res = &types.WaitForUpdatesExResponse{
   882  		Returnval: set,
   883  	}
   884  
   885  	if pc.pending != nil {
   886  		body.Res.Returnval = pc.pending
   887  		pc.pending = pageUpdateSet(body.Res.Returnval, maxObject)
   888  		return body
   889  	}
   890  
   891  	apply := func() bool {
   892  		if fault := pc.apply(ctx, set); fault != nil {
   893  			body.Fault_ = Fault("", fault)
   894  			body.Res = nil
   895  			return false
   896  		}
   897  		return true
   898  	}
   899  
   900  	if r.Version == "" {
   901  		ctx.Map.AddHandler(pc) // Listen for create, update, delete of managed objects
   902  		apply()                // Collect current state
   903  		set.Version = "-"      // Next request with Version set will wait via loop below
   904  		if body.Res != nil {
   905  			pc.pending = pageUpdateSet(body.Res.Returnval, maxObject)
   906  		}
   907  		return body
   908  	}
   909  
   910  	ticker := time.NewTicker(20 * time.Millisecond) // allow for updates to accumulate
   911  	defer ticker.Stop()
   912  	// Start the wait loop, returning on one of:
   913  	// - Client calls CancelWaitForUpdates
   914  	// - MaxWaitSeconds was specified and has been exceeded
   915  	// - We have updates to send to the client
   916  	for {
   917  		select {
   918  		case <-wait.Done():
   919  			body.Res.Returnval = nil
   920  			switch wait.Err() {
   921  			case context.Canceled:
   922  				tracef("%s: WaitForUpdates canceled", pc.Self)
   923  				body.Fault_ = Fault("", new(types.RequestCanceled)) // CancelWaitForUpdates was called
   924  				body.Res = nil
   925  			case context.DeadlineExceeded:
   926  				tracef("%s: WaitForUpdates MaxWaitSeconds exceeded", pc.Self)
   927  			}
   928  
   929  			return body
   930  		case <-ticker.C:
   931  			pc.mu.Lock()
   932  			updates := pc.updates
   933  			pc.updates = nil // clear updates collected by the managed object CRUD listeners
   934  			pc.mu.Unlock()
   935  			if len(updates) == 0 {
   936  				if oneUpdate {
   937  					body.Res.Returnval = nil
   938  					return body
   939  				}
   940  				continue
   941  			}
   942  
   943  			tracef("%s: applying %d updates to %d filters", pc.Self, len(updates), len(pc.Filter))
   944  
   945  			for _, f := range pc.Filter {
   946  				filter := ctx.Session.Get(f).(*PropertyFilter)
   947  				filter.update(ctx)
   948  				fu := types.PropertyFilterUpdate{Filter: f}
   949  
   950  				for _, update := range updates {
   951  					switch update.Kind {
   952  					case types.ObjectUpdateKindEnter: // Create
   953  						if !apply() {
   954  							return body
   955  						}
   956  					case types.ObjectUpdateKindModify: // Update
   957  						tracef("%s has %d changes", update.Obj, len(update.ChangeSet))
   958  						if !apply() { // An update may apply to collector traversal specs
   959  							return body
   960  						}
   961  						if _, ok := filter.refs[update.Obj]; ok {
   962  							// This object has already been applied by the filter,
   963  							// now check if the property spec applies for this update.
   964  							update = filter.apply(ctx, update)
   965  							if len(update.ChangeSet) != 0 {
   966  								fu.ObjectSet = append(fu.ObjectSet, update)
   967  							}
   968  						}
   969  					case types.ObjectUpdateKindLeave: // Delete
   970  						if _, ok := filter.refs[update.Obj]; !ok {
   971  							continue
   972  						}
   973  						delete(filter.refs, update.Obj)
   974  						fu.ObjectSet = append(fu.ObjectSet, update)
   975  					}
   976  				}
   977  
   978  				if len(fu.ObjectSet) != 0 {
   979  					set.FilterSet = append(set.FilterSet, fu)
   980  				}
   981  			}
   982  			if len(set.FilterSet) != 0 {
   983  				pc.pending = pageUpdateSet(body.Res.Returnval, maxObject)
   984  				return body
   985  			}
   986  			if oneUpdate {
   987  				body.Res.Returnval = nil
   988  				return body
   989  			}
   990  		}
   991  	}
   992  }
   993  
   994  // WaitForUpdates is deprecated, but pyvmomi is still using it at the moment.
   995  func (pc *PropertyCollector) WaitForUpdates(ctx *Context, r *types.WaitForUpdates) soap.HasFault {
   996  	body := &methods.WaitForUpdatesBody{}
   997  
   998  	res := pc.WaitForUpdatesEx(ctx, &types.WaitForUpdatesEx{
   999  		This:    r.This,
  1000  		Version: r.Version,
  1001  	})
  1002  
  1003  	if res.Fault() != nil {
  1004  		body.Fault_ = res.Fault()
  1005  	} else {
  1006  		body.Res = &types.WaitForUpdatesResponse{
  1007  			Returnval: *res.(*methods.WaitForUpdatesExBody).Res.Returnval,
  1008  		}
  1009  	}
  1010  
  1011  	return body
  1012  }
  1013  
  1014  // Fetch is not documented in the vSphere SDK, but ovftool depends on it.
  1015  // A Fetch request is converted to a RetrievePropertiesEx method call by vcsim.
  1016  func (pc *PropertyCollector) Fetch(ctx *Context, req *internal.Fetch) soap.HasFault {
  1017  	body := new(internal.FetchBody)
  1018  
  1019  	if req.This == vim25.ServiceInstance && req.Prop == "content" {
  1020  		content := ctx.Map.content()
  1021  		// ovftool uses API version for 6.0 and fails when these fields are non-nil; TODO
  1022  		content.VStorageObjectManager = nil
  1023  		content.HostProfileManager = nil
  1024  		content.HostSpecManager = nil
  1025  		content.CryptoManager = nil
  1026  		content.HostProfileManager = nil
  1027  		content.HealthUpdateManager = nil
  1028  		content.FailoverClusterConfigurator = nil
  1029  		content.FailoverClusterManager = nil
  1030  		body.Res = &internal.FetchResponse{
  1031  			Returnval: content,
  1032  		}
  1033  		return body
  1034  	}
  1035  
  1036  	if ctx.Map.Get(req.This) == nil {
  1037  		// The Fetch method supports use of super class types, this is a quick hack to support the cases used by ovftool
  1038  		switch req.This.Type {
  1039  		case "ManagedEntity":
  1040  			for o := range ctx.Map.objects {
  1041  				if o.Value == req.This.Value {
  1042  					req.This.Type = o.Type
  1043  					break
  1044  				}
  1045  			}
  1046  		case "ComputeResource":
  1047  			req.This.Type = "Cluster" + req.This.Type
  1048  		}
  1049  	}
  1050  
  1051  	res := pc.RetrievePropertiesEx(ctx, &types.RetrievePropertiesEx{
  1052  		SpecSet: []types.PropertyFilterSpec{{
  1053  			PropSet: []types.PropertySpec{{
  1054  				Type:    req.This.Type,
  1055  				PathSet: []string{req.Prop},
  1056  			}},
  1057  			ObjectSet: []types.ObjectSpec{{
  1058  				Obj: req.This,
  1059  			}},
  1060  		}}})
  1061  
  1062  	if res.Fault() != nil {
  1063  		return res
  1064  	}
  1065  
  1066  	obj := res.(*methods.RetrievePropertiesExBody).Res.Returnval.Objects[0]
  1067  	if len(obj.PropSet) == 0 {
  1068  		if len(obj.MissingSet) > 0 {
  1069  			fault := obj.MissingSet[0].Fault
  1070  			body.Fault_ = Fault(fault.LocalizedMessage, fault.Fault)
  1071  			return body
  1072  		}
  1073  		return res
  1074  	}
  1075  
  1076  	body.Res = &internal.FetchResponse{
  1077  		Returnval: obj.PropSet[0].Val,
  1078  	}
  1079  	return body
  1080  }