github.com/vmware/govmomi@v0.43.0/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 }