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