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