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