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 }