github.com/vmware/govmomi@v0.51.0/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 property
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"sync"
    12  
    13  	"github.com/vmware/govmomi/vim25"
    14  	"github.com/vmware/govmomi/vim25/methods"
    15  	"github.com/vmware/govmomi/vim25/mo"
    16  	"github.com/vmware/govmomi/vim25/soap"
    17  	"github.com/vmware/govmomi/vim25/types"
    18  )
    19  
    20  // ErrConcurrentCollector is returned from WaitForUpdates, WaitForUpdatesEx,
    21  // or CheckForUpdates if any of those calls are unable to obtain an exclusive
    22  // lock for the property collector.
    23  var ErrConcurrentCollector = fmt.Errorf(
    24  	"only one goroutine may invoke WaitForUpdates, WaitForUpdatesEx, " +
    25  		"or CheckForUpdates on a given PropertyCollector")
    26  
    27  // Collector models the PropertyCollector managed object.
    28  //
    29  // For more information, see:
    30  // https://developer.broadcom.com/xapis/vsphere-web-services-api/latest/vmodl.query.PropertyCollector.html
    31  type Collector struct {
    32  	mu           sync.Mutex
    33  	roundTripper soap.RoundTripper
    34  	reference    types.ManagedObjectReference
    35  }
    36  
    37  // DefaultCollector returns the session's default property collector.
    38  func DefaultCollector(c *vim25.Client) *Collector {
    39  	p := Collector{
    40  		roundTripper: c,
    41  		reference:    c.ServiceContent.PropertyCollector,
    42  	}
    43  
    44  	return &p
    45  }
    46  
    47  func (p *Collector) Reference() types.ManagedObjectReference {
    48  	return p.reference
    49  }
    50  
    51  // Create creates a new session-specific Collector that can be used to
    52  // retrieve property updates independent of any other Collector.
    53  func (p *Collector) Create(ctx context.Context) (*Collector, error) {
    54  	req := types.CreatePropertyCollector{
    55  		This: p.Reference(),
    56  	}
    57  
    58  	res, err := methods.CreatePropertyCollector(ctx, p.roundTripper, &req)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	newp := Collector{
    64  		roundTripper: p.roundTripper,
    65  		reference:    res.Returnval,
    66  	}
    67  
    68  	return &newp, nil
    69  }
    70  
    71  // Destroy destroys this Collector.
    72  func (p *Collector) Destroy(ctx context.Context) error {
    73  	req := types.DestroyPropertyCollector{
    74  		This: p.Reference(),
    75  	}
    76  
    77  	_, err := methods.DestroyPropertyCollector(ctx, p.roundTripper, &req)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	p.reference = types.ManagedObjectReference{}
    83  	return nil
    84  }
    85  
    86  func (p *Collector) CreateFilter(ctx context.Context, req types.CreateFilter) (*Filter, error) {
    87  	req.This = p.Reference()
    88  
    89  	resp, err := methods.CreateFilter(ctx, p.roundTripper, &req)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	return &Filter{roundTripper: p.roundTripper, reference: resp.Returnval}, nil
    95  }
    96  
    97  // Deprecated: Please use WaitForUpdatesEx instead.
    98  func (p *Collector) WaitForUpdates(
    99  	ctx context.Context,
   100  	version string,
   101  	opts ...*types.WaitOptions) (*types.UpdateSet, error) {
   102  
   103  	if !p.mu.TryLock() {
   104  		return nil, ErrConcurrentCollector
   105  	}
   106  	defer p.mu.Unlock()
   107  
   108  	req := types.WaitForUpdatesEx{
   109  		This:    p.Reference(),
   110  		Version: version,
   111  	}
   112  
   113  	if len(opts) == 1 {
   114  		req.Options = opts[0]
   115  	} else if len(opts) > 1 {
   116  		panic("only one option may be specified")
   117  	}
   118  
   119  	res, err := methods.WaitForUpdatesEx(ctx, p.roundTripper, &req)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	return res.Returnval, nil
   125  }
   126  
   127  func (p *Collector) CancelWaitForUpdates(ctx context.Context) error {
   128  	req := &types.CancelWaitForUpdates{This: p.Reference()}
   129  	_, err := methods.CancelWaitForUpdates(ctx, p.roundTripper, req)
   130  	return err
   131  }
   132  
   133  // RetrieveProperties wraps RetrievePropertiesEx and ContinueRetrievePropertiesEx to collect properties in batches.
   134  func (p *Collector) RetrieveProperties(
   135  	ctx context.Context,
   136  	req types.RetrieveProperties,
   137  	maxObjectsArgs ...int32) (*types.RetrievePropertiesResponse, error) {
   138  
   139  	var opts types.RetrieveOptions
   140  	if l := len(maxObjectsArgs); l > 1 {
   141  		return nil, fmt.Errorf("maxObjectsArgs accepts a single value")
   142  	} else if l == 1 {
   143  		opts.MaxObjects = maxObjectsArgs[0]
   144  	}
   145  
   146  	objects, err := mo.RetrievePropertiesEx(ctx, p.roundTripper, types.RetrievePropertiesEx{
   147  		This:    p.Reference(),
   148  		SpecSet: req.SpecSet,
   149  		Options: opts,
   150  	})
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	return &types.RetrievePropertiesResponse{Returnval: objects}, nil
   156  }
   157  
   158  // Retrieve loads properties for a slice of managed objects. The dst argument
   159  // must be a pointer to a []interface{}, which is populated with the instances
   160  // of the specified managed objects, with the relevant properties filled in. If
   161  // the properties slice is nil, all properties are loaded.
   162  // Note that pointer types are optional fields that may be left as a nil value.
   163  // The caller should check such fields for a nil value before dereferencing.
   164  func (p *Collector) Retrieve(ctx context.Context, objs []types.ManagedObjectReference, ps []string, dst any) error {
   165  	if len(objs) == 0 {
   166  		return errors.New("object references is empty")
   167  	}
   168  
   169  	kinds := make(map[string]bool)
   170  
   171  	var propSet []types.PropertySpec
   172  	var objectSet []types.ObjectSpec
   173  
   174  	for _, obj := range objs {
   175  		if _, ok := kinds[obj.Type]; !ok {
   176  			spec := types.PropertySpec{
   177  				Type: obj.Type,
   178  			}
   179  			if len(ps) == 0 {
   180  				spec.All = types.NewBool(true)
   181  			} else {
   182  				spec.PathSet = ps
   183  			}
   184  			propSet = append(propSet, spec)
   185  			kinds[obj.Type] = true
   186  		}
   187  
   188  		objectSpec := types.ObjectSpec{
   189  			Obj:  obj,
   190  			Skip: types.NewBool(false),
   191  		}
   192  
   193  		objectSet = append(objectSet, objectSpec)
   194  	}
   195  
   196  	req := types.RetrieveProperties{
   197  		SpecSet: []types.PropertyFilterSpec{
   198  			{
   199  				ObjectSet: objectSet,
   200  				PropSet:   propSet,
   201  			},
   202  		},
   203  	}
   204  
   205  	res, err := p.RetrieveProperties(ctx, req)
   206  	if err != nil {
   207  		return err
   208  	}
   209  
   210  	if d, ok := dst.(*[]types.ObjectContent); ok {
   211  		*d = res.Returnval
   212  		return nil
   213  	}
   214  
   215  	return mo.LoadObjectContent(res.Returnval, dst)
   216  }
   217  
   218  // RetrieveWithFilter populates dst as Retrieve does, but only for entities
   219  // that match the specified filter.
   220  func (p *Collector) RetrieveWithFilter(
   221  	ctx context.Context,
   222  	objs []types.ManagedObjectReference,
   223  	ps []string,
   224  	dst any,
   225  	filter Match) error {
   226  
   227  	if len(filter) == 0 {
   228  		return p.Retrieve(ctx, objs, ps, dst)
   229  	}
   230  
   231  	var content []types.ObjectContent
   232  
   233  	err := p.Retrieve(ctx, objs, filter.Keys(), &content)
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	objs = filter.ObjectContent(content)
   239  
   240  	if len(objs) == 0 {
   241  		return nil
   242  	}
   243  
   244  	return p.Retrieve(ctx, objs, ps, dst)
   245  }
   246  
   247  // RetrieveOne calls Retrieve with a single managed object reference via Collector.Retrieve().
   248  func (p *Collector) RetrieveOne(ctx context.Context, obj types.ManagedObjectReference, ps []string, dst any) error {
   249  	var objs = []types.ManagedObjectReference{obj}
   250  	return p.Retrieve(ctx, objs, ps, dst)
   251  }
   252  
   253  // WaitForUpdatesEx waits for any of the specified properties of the specified
   254  // managed object to change. It calls the specified function for every update it
   255  // receives an update - including the empty filter set, which can occur if no
   256  // objects are eligible for updates.
   257  //
   258  // If this function returns false, it continues waiting for
   259  // subsequent updates. If this function returns true, it stops waiting and
   260  // returns upon receiving the first non-empty filter set.
   261  //
   262  // If the Context is canceled, a call to CancelWaitForUpdates() is made and its
   263  // error value is returned.
   264  //
   265  // By default, ObjectUpdate.MissingSet faults are not propagated to the returned
   266  // error, set WaitFilter.PropagateMissing=true to enable MissingSet fault
   267  // propagation.
   268  func (p *Collector) WaitForUpdatesEx(
   269  	ctx context.Context,
   270  	opts *WaitOptions,
   271  	onUpdatesFn func([]types.ObjectUpdate) bool) error {
   272  
   273  	if !p.mu.TryLock() {
   274  		return ErrConcurrentCollector
   275  	}
   276  	defer p.mu.Unlock()
   277  
   278  	req := types.WaitForUpdatesEx{
   279  		This:    p.Reference(),
   280  		Options: opts.Options,
   281  	}
   282  
   283  	for {
   284  		res, err := methods.WaitForUpdatesEx(ctx, p.roundTripper, &req)
   285  		if err != nil {
   286  			if ctx.Err() == context.Canceled {
   287  				return p.CancelWaitForUpdates(context.Background())
   288  			}
   289  			return err
   290  		}
   291  
   292  		set := res.Returnval
   293  		if set == nil {
   294  			if req.Options != nil && req.Options.MaxWaitSeconds != nil {
   295  				return nil // WaitOptions.MaxWaitSeconds exceeded
   296  			}
   297  			// Retry if the result came back empty
   298  			continue
   299  		}
   300  
   301  		req.Version = set.Version
   302  		opts.Truncated = false
   303  		if set.Truncated != nil {
   304  			opts.Truncated = *set.Truncated
   305  		}
   306  
   307  		if len(set.FilterSet) == 0 {
   308  			// Trigger callbacks in case callers need to be notified
   309  			// of the empty filter set.
   310  			_ = onUpdatesFn(make([]types.ObjectUpdate, 0))
   311  		}
   312  
   313  		for _, fs := range set.FilterSet {
   314  			if opts.PropagateMissing {
   315  				for i := range fs.ObjectSet {
   316  					for _, p := range fs.ObjectSet[i].MissingSet {
   317  						// Same behavior as mo.ObjectContentToType()
   318  						return soap.WrapVimFault(p.Fault.Fault)
   319  					}
   320  				}
   321  			}
   322  
   323  			if onUpdatesFn(fs.ObjectSet) {
   324  				return nil
   325  			}
   326  		}
   327  	}
   328  }