github.com/vmware/govmomi@v0.37.1/list/lister.go (about)

     1  /*
     2  Copyright (c) 2014-2023 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 list
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"path"
    23  	"reflect"
    24  
    25  	"github.com/vmware/govmomi/property"
    26  	"github.com/vmware/govmomi/vim25/mo"
    27  	"github.com/vmware/govmomi/vim25/soap"
    28  	"github.com/vmware/govmomi/vim25/types"
    29  )
    30  
    31  type Element struct {
    32  	Path   string
    33  	Object mo.Reference
    34  }
    35  
    36  func (e Element) String() string {
    37  	return fmt.Sprintf("%s @ %s", e.Object.Reference(), e.Path)
    38  }
    39  
    40  func ToElement(r mo.Reference, prefix string) Element {
    41  	var name string
    42  
    43  	// Comments about types to be expected in folders copied from the
    44  	// documentation of the Folder managed object:
    45  	// http://pubs.vmware.com/vsphere-55/topic/com.vmware.wssdk.apiref.doc/vim.Folder.html
    46  	switch m := r.(type) {
    47  	case mo.Folder:
    48  		name = m.Name
    49  	case mo.StoragePod:
    50  		name = m.Name
    51  
    52  	// { "vim.Datacenter" } - Identifies the root folder and its descendant
    53  	// folders. Data center folders can contain child data center folders and
    54  	// Datacenter managed objects. Datacenter objects contain virtual machine,
    55  	// compute resource, network entity, and datastore folders.
    56  	case mo.Datacenter:
    57  		name = m.Name
    58  
    59  	// { "vim.Virtualmachine", "vim.VirtualApp" } - Identifies a virtual machine
    60  	// folder. A virtual machine folder may contain child virtual machine
    61  	// folders. It also can contain VirtualMachine managed objects, templates,
    62  	// and VirtualApp managed objects.
    63  	case mo.VirtualMachine:
    64  		name = m.Name
    65  	case mo.VirtualApp:
    66  		name = m.Name
    67  
    68  	// { "vim.ComputeResource" } - Identifies a compute resource
    69  	// folder, which contains child compute resource folders and ComputeResource
    70  	// hierarchies.
    71  	case mo.ComputeResource:
    72  		name = m.Name
    73  	case mo.ClusterComputeResource:
    74  		name = m.Name
    75  	case mo.HostSystem:
    76  		name = m.Name
    77  	case mo.ResourcePool:
    78  		name = m.Name
    79  
    80  	// { "vim.Network" } - Identifies a network entity folder.
    81  	// Network entity folders on a vCenter Server can contain Network,
    82  	// DistributedVirtualSwitch, and DistributedVirtualPortgroup managed objects.
    83  	// Network entity folders on an ESXi host can contain only Network objects.
    84  	case mo.Network:
    85  		name = m.Name
    86  	case mo.OpaqueNetwork:
    87  		name = m.Name
    88  	case mo.DistributedVirtualSwitch:
    89  		name = m.Name
    90  	case mo.DistributedVirtualPortgroup:
    91  		name = m.Name
    92  	case mo.VmwareDistributedVirtualSwitch:
    93  		name = m.Name
    94  
    95  	// { "vim.Datastore" } - Identifies a datastore folder. Datastore folders can
    96  	// contain child datastore folders and Datastore managed objects.
    97  	case mo.Datastore:
    98  		name = m.Name
    99  
   100  	default:
   101  		panic("not implemented for type " + reflect.TypeOf(r).String())
   102  	}
   103  
   104  	e := Element{
   105  		Path:   path.Join(prefix, name),
   106  		Object: r,
   107  	}
   108  
   109  	return e
   110  }
   111  
   112  type Lister struct {
   113  	Collector *property.Collector
   114  	Reference types.ManagedObjectReference
   115  	Prefix    string
   116  	All       bool
   117  }
   118  
   119  func (l Lister) retrieveProperties(ctx context.Context, req types.RetrieveProperties, dst *[]interface{}) error {
   120  	res, err := l.Collector.RetrieveProperties(ctx, req)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	// Instead of using mo.LoadRetrievePropertiesResponse, use a custom loop to
   126  	// iterate over the results and ignore entries that have properties that
   127  	// could not be retrieved (a non-empty `missingSet` property). Since the
   128  	// returned objects are enumerated by vSphere in the first place, any object
   129  	// that has a non-empty `missingSet` property is indicative of a race
   130  	// condition in vSphere where the object was enumerated initially, but was
   131  	// removed before its properties could be collected.
   132  	for _, p := range res.Returnval {
   133  		v, err := mo.ObjectContentToType(p)
   134  		if err != nil {
   135  			// Ignore fault if it is ManagedObjectNotFound
   136  			if soap.IsVimFault(err) {
   137  				switch soap.ToVimFault(err).(type) {
   138  				case *types.ManagedObjectNotFound:
   139  					continue
   140  				}
   141  			}
   142  
   143  			return err
   144  		}
   145  
   146  		*dst = append(*dst, v)
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  func (l Lister) List(ctx context.Context) ([]Element, error) {
   153  	switch l.Reference.Type {
   154  	case "Folder", "StoragePod":
   155  		return l.ListFolder(ctx)
   156  	case "Datacenter":
   157  		return l.ListDatacenter(ctx)
   158  	case "ComputeResource", "ClusterComputeResource":
   159  		// Treat ComputeResource and ClusterComputeResource as one and the same.
   160  		// It doesn't matter from the perspective of the lister.
   161  		return l.ListComputeResource(ctx)
   162  	case "ResourcePool":
   163  		return l.ListResourcePool(ctx)
   164  	case "HostSystem":
   165  		return l.ListHostSystem(ctx)
   166  	case "VirtualApp":
   167  		return l.ListVirtualApp(ctx)
   168  	case "VmwareDistributedVirtualSwitch", "DistributedVirtualSwitch":
   169  		return l.ListDistributedVirtualSwitch(ctx)
   170  	default:
   171  		return nil, fmt.Errorf("cannot traverse type " + l.Reference.Type)
   172  	}
   173  }
   174  
   175  func (l Lister) ListFolder(ctx context.Context) ([]Element, error) {
   176  	spec := types.PropertyFilterSpec{
   177  		ObjectSet: []types.ObjectSpec{
   178  			{
   179  				Obj: l.Reference,
   180  				SelectSet: []types.BaseSelectionSpec{
   181  					&types.TraversalSpec{
   182  						Path: "childEntity",
   183  						Skip: types.NewBool(false),
   184  						Type: "Folder",
   185  					},
   186  				},
   187  				Skip: types.NewBool(true),
   188  			},
   189  		},
   190  	}
   191  
   192  	// Retrieve all objects that we can deal with
   193  	childTypes := []string{
   194  		"Folder",
   195  		"Datacenter",
   196  		"VirtualApp",
   197  		"VirtualMachine",
   198  		"Network",
   199  		"ComputeResource",
   200  		"ClusterComputeResource",
   201  		"Datastore",
   202  		"DistributedVirtualSwitch",
   203  	}
   204  
   205  	for _, t := range childTypes {
   206  		pspec := types.PropertySpec{
   207  			Type: t,
   208  		}
   209  
   210  		if l.All {
   211  			pspec.All = types.NewBool(true)
   212  		} else {
   213  			pspec.PathSet = []string{"name"}
   214  
   215  			// Additional basic properties.
   216  			switch t {
   217  			case "Folder":
   218  				pspec.PathSet = append(pspec.PathSet, "childType")
   219  			case "ComputeResource", "ClusterComputeResource":
   220  				// The ComputeResource and ClusterComputeResource are dereferenced in
   221  				// the ResourcePoolFlag. Make sure they always have their resourcePool
   222  				// field populated.
   223  				pspec.PathSet = append(pspec.PathSet, "resourcePool")
   224  			}
   225  		}
   226  
   227  		spec.PropSet = append(spec.PropSet, pspec)
   228  	}
   229  
   230  	req := types.RetrieveProperties{
   231  		SpecSet: []types.PropertyFilterSpec{spec},
   232  	}
   233  
   234  	var dst []interface{}
   235  
   236  	err := l.retrieveProperties(ctx, req, &dst)
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  
   241  	es := []Element{}
   242  	for _, v := range dst {
   243  		es = append(es, ToElement(v.(mo.Reference), l.Prefix))
   244  	}
   245  
   246  	return es, nil
   247  }
   248  
   249  func (l Lister) ListDatacenter(ctx context.Context) ([]Element, error) {
   250  	ospec := types.ObjectSpec{
   251  		Obj:  l.Reference,
   252  		Skip: types.NewBool(true),
   253  	}
   254  
   255  	// Include every datastore folder in the select set
   256  	fields := []string{
   257  		"vmFolder",
   258  		"hostFolder",
   259  		"datastoreFolder",
   260  		"networkFolder",
   261  	}
   262  
   263  	for _, f := range fields {
   264  		tspec := types.TraversalSpec{
   265  			Path: f,
   266  			Skip: types.NewBool(false),
   267  			Type: "Datacenter",
   268  		}
   269  
   270  		ospec.SelectSet = append(ospec.SelectSet, &tspec)
   271  	}
   272  
   273  	pspec := types.PropertySpec{
   274  		Type: "Folder",
   275  	}
   276  
   277  	if l.All {
   278  		pspec.All = types.NewBool(true)
   279  	} else {
   280  		pspec.PathSet = []string{"name", "childType"}
   281  	}
   282  
   283  	req := types.RetrieveProperties{
   284  		SpecSet: []types.PropertyFilterSpec{
   285  			{
   286  				ObjectSet: []types.ObjectSpec{ospec},
   287  				PropSet:   []types.PropertySpec{pspec},
   288  			},
   289  		},
   290  	}
   291  
   292  	var dst []interface{}
   293  
   294  	err := l.retrieveProperties(ctx, req, &dst)
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  
   299  	es := []Element{}
   300  	for _, v := range dst {
   301  		es = append(es, ToElement(v.(mo.Reference), l.Prefix))
   302  	}
   303  
   304  	return es, nil
   305  }
   306  
   307  func (l Lister) ListComputeResource(ctx context.Context) ([]Element, error) {
   308  	ospec := types.ObjectSpec{
   309  		Obj:  l.Reference,
   310  		Skip: types.NewBool(true),
   311  	}
   312  
   313  	fields := []string{
   314  		"host",
   315  		"network",
   316  		"resourcePool",
   317  	}
   318  
   319  	for _, f := range fields {
   320  		tspec := types.TraversalSpec{
   321  			Path: f,
   322  			Skip: types.NewBool(false),
   323  			Type: "ComputeResource",
   324  		}
   325  
   326  		ospec.SelectSet = append(ospec.SelectSet, &tspec)
   327  	}
   328  
   329  	childTypes := []string{
   330  		"HostSystem",
   331  		"Network",
   332  		"ResourcePool",
   333  	}
   334  
   335  	var pspecs []types.PropertySpec
   336  	for _, t := range childTypes {
   337  		pspec := types.PropertySpec{
   338  			Type: t,
   339  		}
   340  
   341  		if l.All {
   342  			pspec.All = types.NewBool(true)
   343  		} else {
   344  			pspec.PathSet = []string{"name"}
   345  		}
   346  
   347  		pspecs = append(pspecs, pspec)
   348  	}
   349  
   350  	req := types.RetrieveProperties{
   351  		SpecSet: []types.PropertyFilterSpec{
   352  			{
   353  				ObjectSet: []types.ObjectSpec{ospec},
   354  				PropSet:   pspecs,
   355  			},
   356  		},
   357  	}
   358  
   359  	var dst []interface{}
   360  
   361  	err := l.retrieveProperties(ctx, req, &dst)
   362  	if err != nil {
   363  		return nil, err
   364  	}
   365  
   366  	es := []Element{}
   367  	for _, v := range dst {
   368  		es = append(es, ToElement(v.(mo.Reference), l.Prefix))
   369  	}
   370  
   371  	return es, nil
   372  }
   373  
   374  func (l Lister) ListResourcePool(ctx context.Context) ([]Element, error) {
   375  	ospec := types.ObjectSpec{
   376  		Obj:  l.Reference,
   377  		Skip: types.NewBool(true),
   378  	}
   379  
   380  	fields := []string{
   381  		"resourcePool",
   382  	}
   383  
   384  	for _, f := range fields {
   385  		tspec := types.TraversalSpec{
   386  			Path: f,
   387  			Skip: types.NewBool(false),
   388  			Type: "ResourcePool",
   389  		}
   390  
   391  		ospec.SelectSet = append(ospec.SelectSet, &tspec)
   392  	}
   393  
   394  	childTypes := []string{
   395  		"ResourcePool",
   396  	}
   397  
   398  	var pspecs []types.PropertySpec
   399  	for _, t := range childTypes {
   400  		pspec := types.PropertySpec{
   401  			Type: t,
   402  		}
   403  
   404  		if l.All {
   405  			pspec.All = types.NewBool(true)
   406  		} else {
   407  			pspec.PathSet = []string{"name"}
   408  		}
   409  
   410  		pspecs = append(pspecs, pspec)
   411  	}
   412  
   413  	req := types.RetrieveProperties{
   414  		SpecSet: []types.PropertyFilterSpec{
   415  			{
   416  				ObjectSet: []types.ObjectSpec{ospec},
   417  				PropSet:   pspecs,
   418  			},
   419  		},
   420  	}
   421  
   422  	var dst []interface{}
   423  
   424  	err := l.retrieveProperties(ctx, req, &dst)
   425  	if err != nil {
   426  		return nil, err
   427  	}
   428  
   429  	es := []Element{}
   430  	for _, v := range dst {
   431  		es = append(es, ToElement(v.(mo.Reference), l.Prefix))
   432  	}
   433  
   434  	return es, nil
   435  }
   436  
   437  func (l Lister) ListHostSystem(ctx context.Context) ([]Element, error) {
   438  	ospec := types.ObjectSpec{
   439  		Obj:  l.Reference,
   440  		Skip: types.NewBool(true),
   441  	}
   442  
   443  	fields := []string{
   444  		"datastore",
   445  		"network",
   446  		"vm",
   447  	}
   448  
   449  	for _, f := range fields {
   450  		tspec := types.TraversalSpec{
   451  			Path: f,
   452  			Skip: types.NewBool(false),
   453  			Type: "HostSystem",
   454  		}
   455  
   456  		ospec.SelectSet = append(ospec.SelectSet, &tspec)
   457  	}
   458  
   459  	childTypes := []string{
   460  		"Datastore",
   461  		"Network",
   462  		"VirtualMachine",
   463  	}
   464  
   465  	var pspecs []types.PropertySpec
   466  	for _, t := range childTypes {
   467  		pspec := types.PropertySpec{
   468  			Type: t,
   469  		}
   470  
   471  		if l.All {
   472  			pspec.All = types.NewBool(true)
   473  		} else {
   474  			pspec.PathSet = []string{"name"}
   475  		}
   476  
   477  		pspecs = append(pspecs, pspec)
   478  	}
   479  
   480  	req := types.RetrieveProperties{
   481  		SpecSet: []types.PropertyFilterSpec{
   482  			{
   483  				ObjectSet: []types.ObjectSpec{ospec},
   484  				PropSet:   pspecs,
   485  			},
   486  		},
   487  	}
   488  
   489  	var dst []interface{}
   490  
   491  	err := l.retrieveProperties(ctx, req, &dst)
   492  	if err != nil {
   493  		return nil, err
   494  	}
   495  
   496  	es := []Element{}
   497  	for _, v := range dst {
   498  		es = append(es, ToElement(v.(mo.Reference), l.Prefix))
   499  	}
   500  
   501  	return es, nil
   502  }
   503  
   504  func (l Lister) ListDistributedVirtualSwitch(ctx context.Context) ([]Element, error) {
   505  	ospec := types.ObjectSpec{
   506  		Obj:  l.Reference,
   507  		Skip: types.NewBool(true),
   508  	}
   509  
   510  	fields := []string{
   511  		"portgroup",
   512  	}
   513  
   514  	for _, f := range fields {
   515  		tspec := types.TraversalSpec{
   516  			Path: f,
   517  			Skip: types.NewBool(false),
   518  			Type: "DistributedVirtualSwitch",
   519  		}
   520  
   521  		ospec.SelectSet = append(ospec.SelectSet, &tspec)
   522  	}
   523  
   524  	childTypes := []string{
   525  		"DistributedVirtualPortgroup",
   526  	}
   527  
   528  	var pspecs []types.PropertySpec
   529  	for _, t := range childTypes {
   530  		pspec := types.PropertySpec{
   531  			Type: t,
   532  		}
   533  
   534  		if l.All {
   535  			pspec.All = types.NewBool(true)
   536  		} else {
   537  			pspec.PathSet = []string{"name"}
   538  		}
   539  
   540  		pspecs = append(pspecs, pspec)
   541  	}
   542  
   543  	req := types.RetrieveProperties{
   544  		SpecSet: []types.PropertyFilterSpec{
   545  			{
   546  				ObjectSet: []types.ObjectSpec{ospec},
   547  				PropSet:   pspecs,
   548  			},
   549  		},
   550  	}
   551  
   552  	var dst []interface{}
   553  
   554  	err := l.retrieveProperties(ctx, req, &dst)
   555  	if err != nil {
   556  		return nil, err
   557  	}
   558  
   559  	es := []Element{}
   560  	for _, v := range dst {
   561  		es = append(es, ToElement(v.(mo.Reference), l.Prefix))
   562  	}
   563  
   564  	return es, nil
   565  }
   566  
   567  func (l Lister) ListVirtualApp(ctx context.Context) ([]Element, error) {
   568  	ospec := types.ObjectSpec{
   569  		Obj:  l.Reference,
   570  		Skip: types.NewBool(true),
   571  	}
   572  
   573  	fields := []string{
   574  		"resourcePool",
   575  		"vm",
   576  	}
   577  
   578  	for _, f := range fields {
   579  		tspec := types.TraversalSpec{
   580  			Path: f,
   581  			Skip: types.NewBool(false),
   582  			Type: "VirtualApp",
   583  		}
   584  
   585  		ospec.SelectSet = append(ospec.SelectSet, &tspec)
   586  	}
   587  
   588  	childTypes := []string{
   589  		"ResourcePool",
   590  		"VirtualMachine",
   591  	}
   592  
   593  	var pspecs []types.PropertySpec
   594  	for _, t := range childTypes {
   595  		pspec := types.PropertySpec{
   596  			Type: t,
   597  		}
   598  
   599  		if l.All {
   600  			pspec.All = types.NewBool(true)
   601  		} else {
   602  			pspec.PathSet = []string{"name"}
   603  		}
   604  
   605  		pspecs = append(pspecs, pspec)
   606  	}
   607  
   608  	req := types.RetrieveProperties{
   609  		SpecSet: []types.PropertyFilterSpec{
   610  			{
   611  				ObjectSet: []types.ObjectSpec{ospec},
   612  				PropSet:   pspecs,
   613  			},
   614  		},
   615  	}
   616  
   617  	var dst []interface{}
   618  
   619  	err := l.retrieveProperties(ctx, req, &dst)
   620  	if err != nil {
   621  		return nil, err
   622  	}
   623  
   624  	es := []Element{}
   625  	for _, v := range dst {
   626  		es = append(es, ToElement(v.(mo.Reference), l.Prefix))
   627  	}
   628  
   629  	return es, nil
   630  }