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