github.com/vmware/govmomi@v0.37.2/property/collector.go (about)

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