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

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