1/* 2Copyright (c) 2017 VMware, Inc. All Rights Reserved. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package simulator 18 19import ( 20 "errors" 21 "log" 22 "path" 23 "reflect" 24 "strings" 25 26 "github.com/vmware/govmomi/object" 27 "github.com/vmware/govmomi/vim25/methods" 28 "github.com/vmware/govmomi/vim25/mo" 29 "github.com/vmware/govmomi/vim25/soap" 30 "github.com/vmware/govmomi/vim25/types" 31) 32 33type PropertyCollector struct { 34 mo.PropertyCollector 35} 36 37func NewPropertyCollector(ref types.ManagedObjectReference) object.Reference { 38 s := &PropertyCollector{} 39 s.Self = ref 40 return s 41} 42 43var errMissingField = errors.New("missing field") 44var errEmptyField = errors.New("empty field") 45 46func getObject(ctx *Context, ref types.ManagedObjectReference) (reflect.Value, bool) { 47 var obj mo.Reference 48 if ctx.Session == nil { 49 // Even without permissions to access an object or specific fields, RetrieveProperties 50 // returns an ObjectContent response as long as the object exists. See retrieveResult.add() 51 obj = Map.Get(ref) 52 } else { 53 obj = ctx.Session.Get(ref) 54 } 55 56 if obj == nil { 57 return reflect.Value{}, false 58 } 59 60 if ctx.Session == nil && ref.Type == "SessionManager" { 61 // RetrieveProperties on SessionManager without a session always returns empty, 62 // rather than MissingSet + Fault.NotAuthenticated for each field. 63 obj = &mo.SessionManager{Self: ref} 64 } 65 66 // For objects that use internal types that differ from that of the vim25/mo field types. 67 // See EventHistoryCollector for example. 68 type get interface { 69 Get() mo.Reference 70 } 71 if o, ok := obj.(get); ok { 72 obj = o.Get() 73 } 74 75 rval := reflect.ValueOf(obj).Elem() 76 rtype := rval.Type() 77 78 // PropertyCollector is for Managed Object types only (package mo). 79 // If the registry object is not in the mo package, assume it is a wrapper 80 // type where the first field is an embedded mo type. 81 // We need to dig out the mo type for PropSet.All to work properly and 82 // for the case where the type has a field of the same name, for example: 83 // mo.ResourcePool.ResourcePool 84 for { 85 if path.Base(rtype.PkgPath()) != "mo" { 86 if rtype.Kind() != reflect.Struct || rtype.NumField() == 0 { 87 log.Printf("%#v does not have an embedded mo type", ref) 88 return reflect.Value{}, false 89 } 90 rval = rval.Field(0) 91 rtype = rval.Type() 92 } else { 93 break 94 } 95 } 96 97 return rval, true 98} 99 100func fieldValueInterface(f reflect.StructField, rval reflect.Value) interface{} { 101 if rval.Kind() == reflect.Ptr { 102 rval = rval.Elem() 103 } 104 105 pval := rval.Interface() 106 107 if rval.Kind() == reflect.Slice { 108 // Convert slice to types.ArrayOf* 109 switch v := pval.(type) { 110 case []string: 111 pval = &types.ArrayOfString{ 112 String: v, 113 } 114 case []uint8: 115 pval = &types.ArrayOfByte{ 116 Byte: v, 117 } 118 case []int16: 119 pval = &types.ArrayOfShort{ 120 Short: v, 121 } 122 case []int32: 123 pval = &types.ArrayOfInt{ 124 Int: v, 125 } 126 case []int64: 127 pval = &types.ArrayOfLong{ 128 Long: v, 129 } 130 default: 131 kind := f.Type.Elem().Name() 132 // Remove govmomi interface prefix name 133 if strings.HasPrefix(kind, "Base") { 134 kind = kind[4:] 135 } 136 akind, _ := defaultMapType("ArrayOf" + kind) 137 a := reflect.New(akind) 138 a.Elem().FieldByName(kind).Set(rval) 139 pval = a.Interface() 140 } 141 } 142 143 return pval 144} 145 146func fieldValue(rval reflect.Value, p string) (interface{}, error) { 147 var value interface{} 148 fields := strings.Split(p, ".") 149 150 for i, name := range fields { 151 kind := rval.Type().Kind() 152 153 if kind == reflect.Interface { 154 if rval.IsNil() { 155 continue 156 } 157 rval = rval.Elem() 158 kind = rval.Type().Kind() 159 } 160 161 if kind == reflect.Ptr { 162 if rval.IsNil() { 163 continue 164 } 165 rval = rval.Elem() 166 } 167 168 x := ucFirst(name) 169 val := rval.FieldByName(x) 170 if !val.IsValid() { 171 return nil, errMissingField 172 } 173 174 if isEmpty(val) { 175 return nil, errEmptyField 176 } 177 178 if i == len(fields)-1 { 179 ftype, _ := rval.Type().FieldByName(x) 180 value = fieldValueInterface(ftype, val) 181 break 182 } 183 184 rval = val 185 } 186 187 return value, nil 188} 189 190func fieldRefs(f interface{}) []types.ManagedObjectReference { 191 switch fv := f.(type) { 192 case types.ManagedObjectReference: 193 return []types.ManagedObjectReference{fv} 194 case *types.ArrayOfManagedObjectReference: 195 return fv.ManagedObjectReference 196 case nil: 197 // empty field 198 } 199 200 return nil 201} 202 203func isEmpty(rval reflect.Value) bool { 204 switch rval.Kind() { 205 case reflect.Ptr: 206 return rval.IsNil() 207 case reflect.String: 208 return rval.Len() == 0 209 } 210 211 return false 212} 213 214func isTrue(v *bool) bool { 215 return v != nil && *v 216} 217 218func isFalse(v *bool) bool { 219 return v == nil || *v == false 220} 221 222func lcFirst(s string) string { 223 return strings.ToLower(s[:1]) + s[1:] 224} 225 226func ucFirst(s string) string { 227 return strings.ToUpper(s[:1]) + s[1:] 228} 229 230type retrieveResult struct { 231 *types.RetrieveResult 232 req *types.RetrievePropertiesEx 233 collected map[types.ManagedObjectReference]bool 234 specs map[string]*types.TraversalSpec 235} 236 237func (rr *retrieveResult) add(ctx *Context, name string, val types.AnyType, content *types.ObjectContent) { 238 if ctx.Session != nil { 239 content.PropSet = append(content.PropSet, types.DynamicProperty{ 240 Name: name, 241 Val: val, 242 }) 243 return 244 } 245 246 content.MissingSet = append(content.MissingSet, types.MissingProperty{ 247 Path: name, 248 Fault: types.LocalizedMethodFault{Fault: &types.NotAuthenticated{ 249 NoPermission: types.NoPermission{ 250 Object: content.Obj, 251 PrivilegeId: "System.Read", 252 }}, 253 }, 254 }) 255} 256 257func (rr *retrieveResult) collectAll(ctx *Context, rval reflect.Value, rtype reflect.Type, content *types.ObjectContent) { 258 for i := 0; i < rval.NumField(); i++ { 259 val := rval.Field(i) 260 261 f := rtype.Field(i) 262 263 if isEmpty(val) || f.Name == "Self" { 264 continue 265 } 266 267 if f.Anonymous { 268 // recurse into embedded field 269 rr.collectAll(ctx, val, f.Type, content) 270 continue 271 } 272 273 rr.add(ctx, lcFirst(f.Name), fieldValueInterface(f, val), content) 274 } 275} 276 277func (rr *retrieveResult) collectFields(ctx *Context, rval reflect.Value, fields []string, content *types.ObjectContent) { 278 seen := make(map[string]bool) 279 280 for i := range content.PropSet { 281 seen[content.PropSet[i].Name] = true // mark any already collected via embedded field 282 } 283 284 for _, name := range fields { 285 if seen[name] { 286 // rvc 'ls' includes the "name" property twice, then fails with no error message or stack trace 287 // in RbVmomi::VIM::ObjectContent.to_hash_uncached when it sees the 2nd "name" property. 288 continue 289 } 290 seen[name] = true 291 292 val, err := fieldValue(rval, name) 293 294 switch err { 295 case nil, errEmptyField: 296 rr.add(ctx, name, val, content) 297 case errMissingField: 298 content.MissingSet = append(content.MissingSet, types.MissingProperty{ 299 Path: name, 300 Fault: types.LocalizedMethodFault{Fault: &types.InvalidProperty{ 301 Name: name, 302 }}, 303 }) 304 } 305 } 306} 307 308func (rr *retrieveResult) collect(ctx *Context, ref types.ManagedObjectReference) { 309 if rr.collected[ref] { 310 return 311 } 312 313 content := types.ObjectContent{ 314 Obj: ref, 315 } 316 317 rval, ok := getObject(ctx, ref) 318 if !ok { 319 // Possible if a test uses Map.Remove instead of Destroy_Task 320 log.Printf("object %s no longer exists", ref) 321 return 322 } 323 324 rtype := rval.Type() 325 326 for _, spec := range rr.req.SpecSet { 327 for _, p := range spec.PropSet { 328 if p.Type != ref.Type { 329 // e.g. ManagedEntity, ComputeResource 330 field, ok := rtype.FieldByName(p.Type) 331 332 if !(ok && field.Anonymous) { 333 continue 334 } 335 } 336 337 if isTrue(p.All) { 338 rr.collectAll(ctx, rval, rtype, &content) 339 continue 340 } 341 342 rr.collectFields(ctx, rval, p.PathSet, &content) 343 } 344 } 345 346 if len(content.PropSet) != 0 || len(content.MissingSet) != 0 { 347 rr.Objects = append(rr.Objects, content) 348 } 349 350 rr.collected[ref] = true 351} 352 353func (rr *retrieveResult) selectSet(ctx *Context, obj reflect.Value, s []types.BaseSelectionSpec, refs *[]types.ManagedObjectReference) types.BaseMethodFault { 354 for _, ss := range s { 355 ts, ok := ss.(*types.TraversalSpec) 356 if ok { 357 if ts.Name != "" { 358 rr.specs[ts.Name] = ts 359 } 360 } 361 } 362 363 for _, ss := range s { 364 ts, ok := ss.(*types.TraversalSpec) 365 if !ok { 366 ts = rr.specs[ss.GetSelectionSpec().Name] 367 if ts == nil { 368 return &types.InvalidArgument{InvalidProperty: "undefined TraversalSpec name"} 369 } 370 } 371 372 f, _ := fieldValue(obj, ts.Path) 373 374 for _, ref := range fieldRefs(f) { 375 if isFalse(ts.Skip) { 376 *refs = append(*refs, ref) 377 } 378 379 rval, ok := getObject(ctx, ref) 380 if ok { 381 if err := rr.selectSet(ctx, rval, ts.SelectSet, refs); err != nil { 382 return err 383 } 384 } 385 } 386 } 387 388 return nil 389} 390 391func (pc *PropertyCollector) collect(ctx *Context, r *types.RetrievePropertiesEx) (*types.RetrieveResult, types.BaseMethodFault) { 392 var refs []types.ManagedObjectReference 393 394 rr := &retrieveResult{ 395 RetrieveResult: &types.RetrieveResult{}, 396 req: r, 397 collected: make(map[types.ManagedObjectReference]bool), 398 specs: make(map[string]*types.TraversalSpec), 399 } 400 401 // Select object references 402 for _, spec := range r.SpecSet { 403 for _, o := range spec.ObjectSet { 404 rval, ok := getObject(ctx, o.Obj) 405 if !ok { 406 if isFalse(spec.ReportMissingObjectsInResults) { 407 return nil, &types.ManagedObjectNotFound{Obj: o.Obj} 408 } 409 continue 410 } 411 412 if o.SelectSet == nil || isFalse(o.Skip) { 413 refs = append(refs, o.Obj) 414 } 415 416 if err := rr.selectSet(ctx, rval, o.SelectSet, &refs); err != nil { 417 return nil, err 418 } 419 } 420 } 421 422 for _, ref := range refs { 423 rr.collect(ctx, ref) 424 } 425 426 return rr.RetrieveResult, nil 427} 428 429func (pc *PropertyCollector) CreateFilter(ctx *Context, c *types.CreateFilter) soap.HasFault { 430 body := &methods.CreateFilterBody{} 431 432 filter := &PropertyFilter{pc: pc} 433 filter.PartialUpdates = c.PartialUpdates 434 filter.Spec = c.Spec 435 436 pc.Filter = append(pc.Filter, ctx.Session.Put(filter).Reference()) 437 438 body.Res = &types.CreateFilterResponse{ 439 Returnval: filter.Self, 440 } 441 442 return body 443} 444 445func (pc *PropertyCollector) CreatePropertyCollector(ctx *Context, c *types.CreatePropertyCollector) soap.HasFault { 446 body := &methods.CreatePropertyCollectorBody{} 447 448 cpc := &PropertyCollector{} 449 450 body.Res = &types.CreatePropertyCollectorResponse{ 451 Returnval: ctx.Session.Put(cpc).Reference(), 452 } 453 454 return body 455} 456 457func (pc *PropertyCollector) DestroyPropertyCollector(ctx *Context, c *types.DestroyPropertyCollector) soap.HasFault { 458 body := &methods.DestroyPropertyCollectorBody{} 459 460 for _, ref := range pc.Filter { 461 filter := ctx.Session.Get(ref).(*PropertyFilter) 462 filter.DestroyPropertyFilter(&types.DestroyPropertyFilter{This: ref}) 463 } 464 465 ctx.Session.Remove(c.This) 466 467 body.Res = &types.DestroyPropertyCollectorResponse{} 468 469 return body 470} 471 472func (pc *PropertyCollector) RetrievePropertiesEx(ctx *Context, r *types.RetrievePropertiesEx) soap.HasFault { 473 body := &methods.RetrievePropertiesExBody{} 474 475 res, fault := pc.collect(ctx, r) 476 477 if fault != nil { 478 body.Fault_ = Fault("", fault) 479 } else { 480 objects := res.Objects[:0] 481 for _, o := range res.Objects { 482 propSet := o.PropSet[:0] 483 for _, p := range o.PropSet { 484 if p.Val != nil { 485 propSet = append(propSet, p) 486 } 487 } 488 o.PropSet = propSet 489 490 objects = append(objects, o) 491 } 492 res.Objects = objects 493 body.Res = &types.RetrievePropertiesExResponse{ 494 Returnval: res, 495 } 496 } 497 498 return body 499} 500 501// RetrieveProperties is deprecated, but govmomi is still using it at the moment. 502func (pc *PropertyCollector) RetrieveProperties(ctx *Context, r *types.RetrieveProperties) soap.HasFault { 503 body := &methods.RetrievePropertiesBody{} 504 505 res := pc.RetrievePropertiesEx(ctx, &types.RetrievePropertiesEx{ 506 This: r.This, 507 SpecSet: r.SpecSet, 508 }) 509 510 if res.Fault() != nil { 511 body.Fault_ = res.Fault() 512 } else { 513 body.Res = &types.RetrievePropertiesResponse{ 514 Returnval: res.(*methods.RetrievePropertiesExBody).Res.Returnval.Objects, 515 } 516 } 517 518 return body 519} 520 521func (pc *PropertyCollector) CancelWaitForUpdates(r *types.CancelWaitForUpdates) soap.HasFault { 522 return &methods.CancelWaitForUpdatesBody{Res: new(types.CancelWaitForUpdatesResponse)} 523} 524 525func (pc *PropertyCollector) WaitForUpdatesEx(ctx *Context, r *types.WaitForUpdatesEx) soap.HasFault { 526 body := &methods.WaitForUpdatesExBody{} 527 528 // At the moment we need to support Task completion. Handlers can simply set the Task 529 // state before returning and the non-incremental update is enough for the client. 530 // We can wait for incremental updates to simulate timeouts, etc. 531 if r.Version != "" { 532 body.Fault_ = Fault("incremental updates not supported yet", &types.NotSupported{}) 533 return body 534 } 535 536 update := &types.UpdateSet{ 537 Version: "-", 538 } 539 540 for _, ref := range pc.Filter { 541 filter := ctx.Session.Get(ref).(*PropertyFilter) 542 543 r := &types.RetrievePropertiesEx{} 544 r.SpecSet = append(r.SpecSet, filter.Spec) 545 546 res, fault := pc.collect(ctx, r) 547 if fault != nil { 548 body.Fault_ = Fault("", fault) 549 return body 550 } 551 552 fu := types.PropertyFilterUpdate{ 553 Filter: ref, 554 } 555 556 for _, o := range res.Objects { 557 ou := types.ObjectUpdate{ 558 Obj: o.Obj, 559 Kind: types.ObjectUpdateKindEnter, 560 } 561 562 for _, p := range o.PropSet { 563 ou.ChangeSet = append(ou.ChangeSet, types.PropertyChange{ 564 Op: types.PropertyChangeOpAssign, 565 Name: p.Name, 566 Val: p.Val, 567 }) 568 } 569 570 fu.ObjectSet = append(fu.ObjectSet, ou) 571 } 572 573 update.FilterSet = append(update.FilterSet, fu) 574 } 575 576 body.Res = &types.WaitForUpdatesExResponse{ 577 Returnval: update, 578 } 579 580 return body 581} 582 583// WaitForUpdates is deprecated, but pyvmomi is still using it at the moment. 584func (pc *PropertyCollector) WaitForUpdates(ctx *Context, r *types.WaitForUpdates) soap.HasFault { 585 body := &methods.WaitForUpdatesBody{} 586 587 res := pc.WaitForUpdatesEx(ctx, &types.WaitForUpdatesEx{ 588 This: r.This, 589 Version: r.Version, 590 }) 591 592 if res.Fault() != nil { 593 body.Fault_ = res.Fault() 594 } else { 595 body.Res = &types.WaitForUpdatesResponse{ 596 Returnval: *res.(*methods.WaitForUpdatesExBody).Res.Returnval, 597 } 598 } 599 600 return body 601} 602