github.com/vmware/govmomi@v0.37.2/simulator/model.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  	"crypto/tls"
    22  	"fmt"
    23  	"log"
    24  	"math/rand"
    25  	"os"
    26  	"path"
    27  	"path/filepath"
    28  	"reflect"
    29  	"strings"
    30  	"time"
    31  
    32  	"github.com/google/uuid"
    33  
    34  	"github.com/vmware/govmomi"
    35  	"github.com/vmware/govmomi/object"
    36  	"github.com/vmware/govmomi/simulator/esx"
    37  	"github.com/vmware/govmomi/simulator/vpx"
    38  	"github.com/vmware/govmomi/units"
    39  	"github.com/vmware/govmomi/vim25"
    40  	"github.com/vmware/govmomi/vim25/methods"
    41  	"github.com/vmware/govmomi/vim25/mo"
    42  	"github.com/vmware/govmomi/vim25/types"
    43  	"github.com/vmware/govmomi/vim25/xml"
    44  )
    45  
    46  type DelayConfig struct {
    47  	// Delay specifies the number of milliseconds to delay serving a SOAP call. 0 means no delay.
    48  	// This can be used to simulate a poorly performing vCenter or network lag.
    49  	Delay int
    50  
    51  	// Delay specifies the number of milliseconds to delay serving a specific method.
    52  	// Each entry in the map represents the name of a method and its associated delay in milliseconds,
    53  	// This can be used to simulate a poorly performing vCenter or network lag.
    54  	MethodDelay map[string]int
    55  
    56  	// DelayJitter defines the delay jitter as a coefficient of variation (stddev/mean).
    57  	// This can be used to simulate unpredictable delay. 0 means no jitter, i.e. all invocations get the same delay.
    58  	DelayJitter float64
    59  }
    60  
    61  // Model is used to populate a Model with an initial set of managed entities.
    62  // This is a simple helper for tests running against a simulator, to populate an inventory
    63  // with commonly used models.
    64  // The inventory names generated by a Model have a string prefix per-type and integer suffix per-instance.
    65  // The names are concatenated with their ancestor names and delimited by '_', making the generated names unique.
    66  type Model struct {
    67  	Service *Service `json:"-"`
    68  
    69  	ServiceContent types.ServiceContent `json:"-"`
    70  	RootFolder     mo.Folder            `json:"-"`
    71  
    72  	// Autostart will power on Model created VMs when true
    73  	Autostart bool `json:"-"`
    74  
    75  	// Datacenter specifies the number of Datacenter entities to create
    76  	// Name prefix: DC, vcsim flag: -dc
    77  	Datacenter int `json:"datacenter"`
    78  
    79  	// Portgroup specifies the number of DistributedVirtualPortgroup entities to create per Datacenter
    80  	// Name prefix: DVPG, vcsim flag: -pg
    81  	Portgroup int `json:"portgroup"`
    82  
    83  	// PortgroupNSX specifies the number NSX backed DistributedVirtualPortgroup entities to create per Datacenter
    84  	// Name prefix: NSXPG, vcsim flag: -nsx-pg
    85  	PortgroupNSX int `json:"portgroupNSX"`
    86  
    87  	// OpaqueNetwork specifies the number of OpaqueNetwork entities to create per Datacenter,
    88  	// with Summary.OpaqueNetworkType set to nsx.LogicalSwitch and Summary.OpaqueNetworkId to a random uuid.
    89  	// Name prefix: NSX, vcsim flag: -nsx
    90  	OpaqueNetwork int `json:"opaqueNetwork"`
    91  
    92  	// Host specifies the number of standalone HostSystems entities to create per Datacenter
    93  	// Name prefix: H, vcsim flag: -standalone-host
    94  	Host int `json:"host,omitempty"`
    95  
    96  	// Cluster specifies the number of ClusterComputeResource entities to create per Datacenter
    97  	// Name prefix: C, vcsim flag: -cluster
    98  	Cluster int `json:"cluster"`
    99  
   100  	// ClusterHost specifies the number of HostSystems entities to create within a Cluster
   101  	// Name prefix: H, vcsim flag: -host
   102  	ClusterHost int `json:"clusterHost,omitempty"`
   103  
   104  	// Pool specifies the number of ResourcePool entities to create per Cluster
   105  	// Note that every cluster has a root ResourcePool named "Resources", as real vCenter does.
   106  	// For example: /DC0/host/DC0_C0/Resources
   107  	// The root ResourcePool is named "RP0" within other object names.
   108  	// When Model.Pool is set to 1 or higher, this creates child ResourcePools under the root pool.
   109  	// Note that this flag is not effective on standalone hosts.
   110  	// For example: /DC0/host/DC0_C0/Resources/DC0_C0_RP1
   111  	// Name prefix: RP, vcsim flag: -pool
   112  	Pool int `json:"pool"`
   113  
   114  	// Datastore specifies the number of Datastore entities to create
   115  	// Each Datastore will have temporary local file storage and will be mounted
   116  	// on every HostSystem created by the ModelConfig
   117  	// Name prefix: LocalDS, vcsim flag: -ds
   118  	Datastore int `json:"datastore"`
   119  
   120  	// Machine specifies the number of VirtualMachine entities to create per
   121  	// ResourcePool. If the pool flag is specified, the specified number of virtual
   122  	// machines will be deployed to each child pool and prefixed with the child
   123  	// resource pool name. Otherwise they are deployed into the root resource pool,
   124  	// prefixed with RP0. On standalone hosts, machines are always deployed into the
   125  	// root resource pool without any prefix.
   126  	// Name prefix: VM, vcsim flag: -vm
   127  	Machine int `json:"machine"`
   128  
   129  	// Folder specifies the number of Datacenter to place within a Folder.
   130  	// This includes a folder for the Datacenter itself and its host, vm, network and datastore folders.
   131  	// All resources for the Datacenter are placed within these folders, rather than the top-level folders.
   132  	// Name prefix: F, vcsim flag: -folder
   133  	Folder int `json:"folder"`
   134  
   135  	// App specifies the number of VirtualApp to create per Cluster
   136  	// Name prefix: APP, vcsim flag: -app
   137  	App int `json:"app"`
   138  
   139  	// Pod specifies the number of StoragePod to create per Cluster
   140  	// Name prefix: POD, vcsim flag: -pod
   141  	Pod int `json:"pod"`
   142  
   143  	// Delay configurations
   144  	DelayConfig DelayConfig `json:"-"`
   145  
   146  	// total number of inventory objects, set by Count()
   147  	total int
   148  
   149  	dirs []string
   150  }
   151  
   152  // ESX is the default Model for a standalone ESX instance
   153  func ESX() *Model {
   154  	return &Model{
   155  		ServiceContent: esx.ServiceContent,
   156  		RootFolder:     esx.RootFolder,
   157  		Autostart:      true,
   158  		Datastore:      1,
   159  		Machine:        2,
   160  		DelayConfig: DelayConfig{
   161  			Delay:       0,
   162  			DelayJitter: 0,
   163  			MethodDelay: nil,
   164  		},
   165  	}
   166  }
   167  
   168  // VPX is the default Model for a vCenter instance
   169  func VPX() *Model {
   170  	return &Model{
   171  		ServiceContent: vpx.ServiceContent,
   172  		RootFolder:     vpx.RootFolder,
   173  		Autostart:      true,
   174  		Datacenter:     1,
   175  		Portgroup:      1,
   176  		Host:           1,
   177  		Cluster:        1,
   178  		ClusterHost:    3,
   179  		Datastore:      1,
   180  		Machine:        2,
   181  		DelayConfig: DelayConfig{
   182  			Delay:       0,
   183  			DelayJitter: 0,
   184  			MethodDelay: nil,
   185  		},
   186  	}
   187  }
   188  
   189  // Count returns a Model with total number of each existing type
   190  func (m *Model) Count() Model {
   191  	count := Model{}
   192  
   193  	for ref, obj := range Map.objects {
   194  		if _, ok := obj.(mo.Entity); !ok {
   195  			continue
   196  		}
   197  
   198  		count.total++
   199  
   200  		switch ref.Type {
   201  		case "Datacenter":
   202  			count.Datacenter++
   203  		case "DistributedVirtualPortgroup":
   204  			count.Portgroup++
   205  		case "ClusterComputeResource":
   206  			count.Cluster++
   207  		case "Datastore":
   208  			count.Datastore++
   209  		case "HostSystem":
   210  			count.Host++
   211  		case "VirtualMachine":
   212  			count.Machine++
   213  		case "ResourcePool":
   214  			count.Pool++
   215  		case "VirtualApp":
   216  			count.App++
   217  		case "Folder":
   218  			count.Folder++
   219  		case "StoragePod":
   220  			count.Pod++
   221  		case "OpaqueNetwork":
   222  			count.OpaqueNetwork++
   223  		}
   224  	}
   225  
   226  	return count
   227  }
   228  
   229  func (*Model) fmtName(prefix string, num int) string {
   230  	return fmt.Sprintf("%s%d", prefix, num)
   231  }
   232  
   233  // kinds maps managed object types to their vcsim wrapper types
   234  var kinds = map[string]reflect.Type{
   235  	"AuthorizationManager":            reflect.TypeOf((*AuthorizationManager)(nil)).Elem(),
   236  	"ClusterComputeResource":          reflect.TypeOf((*ClusterComputeResource)(nil)).Elem(),
   237  	"CustomFieldsManager":             reflect.TypeOf((*CustomFieldsManager)(nil)).Elem(),
   238  	"CustomizationSpecManager":        reflect.TypeOf((*CustomizationSpecManager)(nil)).Elem(),
   239  	"Datacenter":                      reflect.TypeOf((*Datacenter)(nil)).Elem(),
   240  	"Datastore":                       reflect.TypeOf((*Datastore)(nil)).Elem(),
   241  	"DistributedVirtualPortgroup":     reflect.TypeOf((*DistributedVirtualPortgroup)(nil)).Elem(),
   242  	"DistributedVirtualSwitch":        reflect.TypeOf((*DistributedVirtualSwitch)(nil)).Elem(),
   243  	"DistributedVirtualSwitchManager": reflect.TypeOf((*DistributedVirtualSwitchManager)(nil)).Elem(),
   244  	"EnvironmentBrowser":              reflect.TypeOf((*EnvironmentBrowser)(nil)).Elem(),
   245  	"EventManager":                    reflect.TypeOf((*EventManager)(nil)).Elem(),
   246  	"ExtensionManager":                reflect.TypeOf((*ExtensionManager)(nil)).Elem(),
   247  	"FileManager":                     reflect.TypeOf((*FileManager)(nil)).Elem(),
   248  	"Folder":                          reflect.TypeOf((*Folder)(nil)).Elem(),
   249  	"GuestOperationsManager":          reflect.TypeOf((*GuestOperationsManager)(nil)).Elem(),
   250  	"HostDatastoreBrowser":            reflect.TypeOf((*HostDatastoreBrowser)(nil)).Elem(),
   251  	"HostLocalAccountManager":         reflect.TypeOf((*HostLocalAccountManager)(nil)).Elem(),
   252  	"HostNetworkSystem":               reflect.TypeOf((*HostNetworkSystem)(nil)).Elem(),
   253  	"HostSystem":                      reflect.TypeOf((*HostSystem)(nil)).Elem(),
   254  	"IpPoolManager":                   reflect.TypeOf((*IpPoolManager)(nil)).Elem(),
   255  	"LicenseManager":                  reflect.TypeOf((*LicenseManager)(nil)).Elem(),
   256  	"OptionManager":                   reflect.TypeOf((*OptionManager)(nil)).Elem(),
   257  	"OvfManager":                      reflect.TypeOf((*OvfManager)(nil)).Elem(),
   258  	"PerformanceManager":              reflect.TypeOf((*PerformanceManager)(nil)).Elem(),
   259  	"PropertyCollector":               reflect.TypeOf((*PropertyCollector)(nil)).Elem(),
   260  	"ResourcePool":                    reflect.TypeOf((*ResourcePool)(nil)).Elem(),
   261  	"SearchIndex":                     reflect.TypeOf((*SearchIndex)(nil)).Elem(),
   262  	"SessionManager":                  reflect.TypeOf((*SessionManager)(nil)).Elem(),
   263  	"StoragePod":                      reflect.TypeOf((*StoragePod)(nil)).Elem(),
   264  	"StorageResourceManager":          reflect.TypeOf((*StorageResourceManager)(nil)).Elem(),
   265  	"TaskManager":                     reflect.TypeOf((*TaskManager)(nil)).Elem(),
   266  	"TenantTenantManager":             reflect.TypeOf((*TenantManager)(nil)).Elem(),
   267  	"UserDirectory":                   reflect.TypeOf((*UserDirectory)(nil)).Elem(),
   268  	"VcenterVStorageObjectManager":    reflect.TypeOf((*VcenterVStorageObjectManager)(nil)).Elem(),
   269  	"ViewManager":                     reflect.TypeOf((*ViewManager)(nil)).Elem(),
   270  	"VirtualApp":                      reflect.TypeOf((*VirtualApp)(nil)).Elem(),
   271  	"VirtualDiskManager":              reflect.TypeOf((*VirtualDiskManager)(nil)).Elem(),
   272  	"VirtualMachine":                  reflect.TypeOf((*VirtualMachine)(nil)).Elem(),
   273  	"VmwareDistributedVirtualSwitch":  reflect.TypeOf((*DistributedVirtualSwitch)(nil)).Elem(),
   274  }
   275  
   276  func loadObject(content types.ObjectContent) (mo.Reference, error) {
   277  	var obj mo.Reference
   278  	id := content.Obj
   279  
   280  	kind, ok := kinds[id.Type]
   281  	if ok {
   282  		obj = reflect.New(kind).Interface().(mo.Reference)
   283  	}
   284  
   285  	if obj == nil {
   286  		// No vcsim wrapper for this type, e.g. IoFilterManager
   287  		x, err := mo.ObjectContentToType(content, true)
   288  		if err != nil {
   289  			return nil, err
   290  		}
   291  		obj = x.(mo.Reference)
   292  	} else {
   293  		if len(content.PropSet) == 0 {
   294  			// via NewServiceInstance()
   295  			Map.setReference(obj, id)
   296  		} else {
   297  			// via Model.Load()
   298  			dst := getManagedObject(obj).Addr().Interface().(mo.Reference)
   299  			err := mo.LoadObjectContent([]types.ObjectContent{content}, dst)
   300  			if err != nil {
   301  				return nil, err
   302  			}
   303  		}
   304  
   305  		if x, ok := obj.(interface{ init(*Registry) }); ok {
   306  			x.init(Map)
   307  		}
   308  	}
   309  
   310  	return obj, nil
   311  }
   312  
   313  // resolveReferences attempts to resolve any object references that were not included via Load()
   314  // example: Load's dir only contains a single OpaqueNetwork, we need to create a Datacenter and
   315  // place the OpaqueNetwork in the Datacenter's network folder.
   316  func (m *Model) resolveReferences(ctx *Context) error {
   317  	dc, ok := ctx.Map.Any("Datacenter").(*Datacenter)
   318  	if !ok {
   319  		// Need to have at least 1 Datacenter
   320  		root := ctx.Map.Get(ctx.Map.content().RootFolder).(*Folder)
   321  		ref := root.CreateDatacenter(ctx, &types.CreateDatacenter{
   322  			This: root.Self,
   323  			Name: "DC0",
   324  		}).(*methods.CreateDatacenterBody).Res.Returnval
   325  		dc = ctx.Map.Get(ref).(*Datacenter)
   326  	}
   327  
   328  	for ref, val := range ctx.Map.objects {
   329  		me, ok := val.(mo.Entity)
   330  		if !ok {
   331  			continue
   332  		}
   333  		e := me.Entity()
   334  		if e.Parent == nil || ref.Type == "Folder" {
   335  			continue
   336  		}
   337  		if ctx.Map.Get(*e.Parent) == nil {
   338  			// object was loaded without its parent, attempt to foster with another parent
   339  			switch e.Parent.Type {
   340  			case "Folder":
   341  				folder := dc.folder(me)
   342  				e.Parent = &folder.Self
   343  				log.Printf("%s adopted %s", e.Parent, ref)
   344  				folderPutChild(ctx, folder, me)
   345  			default:
   346  				return fmt.Errorf("unable to foster %s with parent type=%s", ref, e.Parent.Type)
   347  			}
   348  		}
   349  		// TODO: resolve any remaining orphan references via mo.References()
   350  	}
   351  
   352  	return nil
   353  }
   354  
   355  func (m *Model) decode(path string, data interface{}) error {
   356  	f, err := os.Open(path)
   357  	if err != nil {
   358  		return err
   359  	}
   360  
   361  	dec := xml.NewDecoder(f)
   362  	dec.TypeFunc = types.TypeFunc()
   363  	err = dec.Decode(data)
   364  	_ = f.Close()
   365  	return err
   366  }
   367  
   368  func (m *Model) loadMethod(obj mo.Reference, dir string) error {
   369  	dir = filepath.Join(dir, obj.Reference().Encode())
   370  
   371  	info, err := os.ReadDir(dir)
   372  	if err != nil {
   373  		if os.IsNotExist(err) {
   374  			return nil
   375  		}
   376  		return err
   377  	}
   378  
   379  	zero := reflect.Value{}
   380  	for _, x := range info {
   381  		name := strings.TrimSuffix(x.Name(), ".xml") + "Response"
   382  		path := filepath.Join(dir, x.Name())
   383  		response := reflect.ValueOf(obj).Elem().FieldByName(name)
   384  		if response == zero {
   385  			return fmt.Errorf("field %T.%s not found", obj, name)
   386  		}
   387  		if err = m.decode(path, response.Addr().Interface()); err != nil {
   388  			return err
   389  		}
   390  	}
   391  
   392  	return nil
   393  }
   394  
   395  // When simulator code needs to call other simulator code, it typically passes whatever
   396  // context is associated with the request it's servicing.
   397  // Model code isn't servicing a request, but still needs a context, so we spoof
   398  // one for the purposes of calling simulator code.
   399  // Test code also tends to do this.
   400  func SpoofContext() *Context {
   401  	return &Context{
   402  		Context: context.Background(),
   403  		Session: &Session{
   404  			UserSession: types.UserSession{
   405  				Key: uuid.New().String(),
   406  			},
   407  			Registry: NewRegistry(),
   408  		},
   409  		Map: Map,
   410  	}
   411  }
   412  
   413  // Load Model from the given directory, as created by the 'govc object.save' command.
   414  func (m *Model) Load(dir string) error {
   415  	ctx := SpoofContext()
   416  	var s *ServiceInstance
   417  
   418  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   419  		if err != nil {
   420  			return err
   421  		}
   422  		if info.IsDir() {
   423  			if path == dir {
   424  				return nil
   425  			}
   426  			return filepath.SkipDir
   427  		}
   428  		if filepath.Ext(path) != ".xml" {
   429  			return nil
   430  		}
   431  
   432  		var content types.ObjectContent
   433  		err = m.decode(path, &content)
   434  		if err != nil {
   435  			return err
   436  		}
   437  
   438  		if content.Obj == vim25.ServiceInstance {
   439  			s = new(ServiceInstance)
   440  			s.Self = content.Obj
   441  			Map = NewRegistry()
   442  			ctx.Map = Map
   443  			ctx.Map.Put(s)
   444  			return mo.LoadObjectContent([]types.ObjectContent{content}, &s.ServiceInstance)
   445  		}
   446  
   447  		if s == nil {
   448  			s = NewServiceInstance(ctx, m.ServiceContent, m.RootFolder)
   449  			ctx.Map = Map
   450  		}
   451  
   452  		obj, err := loadObject(content)
   453  		if err != nil {
   454  			return err
   455  		}
   456  
   457  		if x, ok := obj.(interface{ model(*Model) error }); ok {
   458  			if err = x.model(m); err != nil {
   459  				return err
   460  			}
   461  		}
   462  
   463  		return m.loadMethod(ctx.Map.Put(obj), dir)
   464  	})
   465  
   466  	if err != nil {
   467  		return err
   468  	}
   469  
   470  	m.Service = New(s)
   471  
   472  	return m.resolveReferences(ctx)
   473  }
   474  
   475  // Create populates the Model with the given ModelConfig
   476  func (m *Model) Create() error {
   477  	ctx := SpoofContext()
   478  	m.Service = New(NewServiceInstance(ctx, m.ServiceContent, m.RootFolder))
   479  	ctx.Map = Map
   480  	return m.CreateInfrastructure(ctx)
   481  }
   482  
   483  func (m *Model) CreateInfrastructure(ctx *Context) error {
   484  	client := m.Service.client
   485  	root := object.NewRootFolder(client)
   486  
   487  	// After all hosts are created, this var is used to mount the host datastores.
   488  	var hosts []*object.HostSystem
   489  	hostMap := make(map[string][]*object.HostSystem)
   490  
   491  	// We need to defer VM creation until after the datastores are created.
   492  	var vms []func() error
   493  	// 1 DVS per DC, added to all hosts
   494  	var dvs *object.DistributedVirtualSwitch
   495  	// 1 NIC per VM, backed by a DVPG if Model.Portgroup > 0
   496  	vmnet := esx.EthernetCard.Backing
   497  
   498  	// addHost adds a cluster host or a standalone host.
   499  	addHost := func(name string, f func(types.HostConnectSpec) (*object.Task, error)) (*object.HostSystem, error) {
   500  		spec := types.HostConnectSpec{
   501  			HostName: name,
   502  		}
   503  
   504  		task, err := f(spec)
   505  		if err != nil {
   506  			return nil, err
   507  		}
   508  
   509  		info, err := task.WaitForResult(context.Background(), nil)
   510  		if err != nil {
   511  			return nil, err
   512  		}
   513  
   514  		host := object.NewHostSystem(client, info.Result.(types.ManagedObjectReference))
   515  		hosts = append(hosts, host)
   516  
   517  		if dvs != nil {
   518  			config := &types.DVSConfigSpec{
   519  				Host: []types.DistributedVirtualSwitchHostMemberConfigSpec{{
   520  					Operation: string(types.ConfigSpecOperationAdd),
   521  					Host:      host.Reference(),
   522  				}},
   523  			}
   524  
   525  			task, _ = dvs.Reconfigure(ctx, config)
   526  			_, _ = task.WaitForResult(context.Background(), nil)
   527  		}
   528  
   529  		return host, nil
   530  	}
   531  
   532  	// addMachine returns a func to create a VM.
   533  	addMachine := func(prefix string, host *object.HostSystem, pool *object.ResourcePool, folders *object.DatacenterFolders) {
   534  		nic := esx.EthernetCard
   535  		nic.Backing = vmnet
   536  		ds := types.ManagedObjectReference{}
   537  
   538  		f := func() error {
   539  			for i := 0; i < m.Machine; i++ {
   540  				name := m.fmtName(prefix+"_VM", i)
   541  
   542  				config := types.VirtualMachineConfigSpec{
   543  					Name:    name,
   544  					GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest),
   545  					Files: &types.VirtualMachineFileInfo{
   546  						VmPathName: "[LocalDS_0]",
   547  					},
   548  				}
   549  
   550  				if pool == nil {
   551  					pool, _ = host.ResourcePool(ctx)
   552  				}
   553  
   554  				var devices object.VirtualDeviceList
   555  
   556  				scsi, _ := devices.CreateSCSIController("pvscsi")
   557  				ide, _ := devices.CreateIDEController()
   558  				cdrom, _ := devices.CreateCdrom(ide.(*types.VirtualIDEController))
   559  				disk := devices.CreateDisk(scsi.(types.BaseVirtualController), ds,
   560  					config.Files.VmPathName+" "+path.Join(name, "disk1.vmdk"))
   561  				disk.CapacityInKB = int64(units.GB*10) / units.KB
   562  				disk.StorageIOAllocation = &types.StorageIOAllocationInfo{Limit: types.NewInt64(-1)}
   563  
   564  				devices = append(devices, scsi, cdrom, disk, &nic)
   565  
   566  				config.DeviceChange, _ = devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
   567  
   568  				task, err := folders.VmFolder.CreateVM(ctx, config, pool, host)
   569  				if err != nil {
   570  					return err
   571  				}
   572  
   573  				info, err := task.WaitForResult(ctx, nil)
   574  				if err != nil {
   575  					return err
   576  				}
   577  
   578  				vm := object.NewVirtualMachine(client, info.Result.(types.ManagedObjectReference))
   579  
   580  				if m.Autostart {
   581  					task, _ = vm.PowerOn(ctx)
   582  					_, _ = task.WaitForResult(ctx, nil)
   583  				}
   584  			}
   585  
   586  			return nil
   587  		}
   588  
   589  		vms = append(vms, f)
   590  	}
   591  
   592  	nfolder := 0
   593  
   594  	for ndc := 0; ndc < m.Datacenter; ndc++ {
   595  		dcName := m.fmtName("DC", ndc)
   596  		folder := root
   597  		fName := m.fmtName("F", nfolder)
   598  
   599  		// If Datacenter > Folder, don't create folders for the first N DCs.
   600  		if nfolder < m.Folder && ndc >= (m.Datacenter-m.Folder) {
   601  			f, err := folder.CreateFolder(ctx, fName)
   602  			if err != nil {
   603  				return err
   604  			}
   605  			folder = f
   606  		}
   607  
   608  		dc, err := folder.CreateDatacenter(ctx, dcName)
   609  		if err != nil {
   610  			return err
   611  		}
   612  
   613  		folders, err := dc.Folders(ctx)
   614  		if err != nil {
   615  			return err
   616  		}
   617  
   618  		if m.Pod > 0 {
   619  			for pod := 0; pod < m.Pod; pod++ {
   620  				_, _ = folders.DatastoreFolder.CreateStoragePod(ctx, m.fmtName(dcName+"_POD", pod))
   621  			}
   622  		}
   623  
   624  		if folder != root {
   625  			// Create sub-folders and use them to create any resources that follow
   626  			subs := []**object.Folder{&folders.DatastoreFolder, &folders.HostFolder, &folders.NetworkFolder, &folders.VmFolder}
   627  
   628  			for _, sub := range subs {
   629  				f, err := (*sub).CreateFolder(ctx, fName)
   630  				if err != nil {
   631  					return err
   632  				}
   633  
   634  				*sub = f
   635  			}
   636  
   637  			nfolder++
   638  		}
   639  
   640  		if m.Portgroup > 0 || m.PortgroupNSX > 0 {
   641  			var spec types.DVSCreateSpec
   642  			spec.ConfigSpec = &types.VMwareDVSConfigSpec{}
   643  			spec.ConfigSpec.GetDVSConfigSpec().Name = m.fmtName("DVS", 0)
   644  
   645  			task, err := folders.NetworkFolder.CreateDVS(ctx, spec)
   646  			if err != nil {
   647  				return err
   648  			}
   649  
   650  			info, err := task.WaitForResult(ctx, nil)
   651  			if err != nil {
   652  				return err
   653  			}
   654  
   655  			dvs = object.NewDistributedVirtualSwitch(client, info.Result.(types.ManagedObjectReference))
   656  		}
   657  
   658  		for npg := 0; npg < m.Portgroup; npg++ {
   659  			name := m.fmtName(dcName+"_DVPG", npg)
   660  			spec := types.DVPortgroupConfigSpec{
   661  				Name:     name,
   662  				Type:     string(types.DistributedVirtualPortgroupPortgroupTypeEarlyBinding),
   663  				NumPorts: 1,
   664  			}
   665  
   666  			task, err := dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{spec})
   667  			if err != nil {
   668  				return err
   669  			}
   670  			if err = task.Wait(ctx); err != nil {
   671  				return err
   672  			}
   673  
   674  			// Use the 1st DVPG for the VMs eth0 backing
   675  			if npg == 0 {
   676  				// AddPortgroup_Task does not return the moid, so we look it up by name
   677  				net := ctx.Map.Get(folders.NetworkFolder.Reference()).(*Folder)
   678  				pg := ctx.Map.FindByName(name, net.ChildEntity)
   679  
   680  				vmnet, _ = object.NewDistributedVirtualPortgroup(client, pg.Reference()).EthernetCardBackingInfo(ctx)
   681  			}
   682  		}
   683  
   684  		for npg := 0; npg < m.PortgroupNSX; npg++ {
   685  			name := m.fmtName(dcName+"_NSXPG", npg)
   686  			spec := types.DVPortgroupConfigSpec{
   687  				Name:        name,
   688  				Type:        string(types.DistributedVirtualPortgroupPortgroupTypeEarlyBinding),
   689  				BackingType: string(types.DistributedVirtualPortgroupBackingTypeNsx),
   690  			}
   691  
   692  			task, err := dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{spec})
   693  			if err != nil {
   694  				return err
   695  			}
   696  			if err = task.Wait(ctx); err != nil {
   697  				return err
   698  			}
   699  		}
   700  
   701  		// Must use simulator methods directly for OpaqueNetwork
   702  		networkFolder := ctx.Map.Get(folders.NetworkFolder.Reference()).(*Folder)
   703  
   704  		for i := 0; i < m.OpaqueNetwork; i++ {
   705  			var summary types.OpaqueNetworkSummary
   706  			summary.Name = m.fmtName(dcName+"_NSX", i)
   707  			err := networkFolder.AddOpaqueNetwork(ctx, summary)
   708  			if err != nil {
   709  				return err
   710  			}
   711  		}
   712  
   713  		for nhost := 0; nhost < m.Host; nhost++ {
   714  			name := m.fmtName(dcName+"_H", nhost)
   715  
   716  			host, err := addHost(name, func(spec types.HostConnectSpec) (*object.Task, error) {
   717  				return folders.HostFolder.AddStandaloneHost(ctx, spec, true, nil, nil)
   718  			})
   719  			if err != nil {
   720  				return err
   721  			}
   722  
   723  			addMachine(name, host, nil, folders)
   724  		}
   725  
   726  		for ncluster := 0; ncluster < m.Cluster; ncluster++ {
   727  			clusterName := m.fmtName(dcName+"_C", ncluster)
   728  
   729  			cluster, err := folders.HostFolder.CreateCluster(ctx, clusterName, types.ClusterConfigSpecEx{})
   730  			if err != nil {
   731  				return err
   732  			}
   733  
   734  			for nhost := 0; nhost < m.ClusterHost; nhost++ {
   735  				name := m.fmtName(clusterName+"_H", nhost)
   736  
   737  				_, err = addHost(name, func(spec types.HostConnectSpec) (*object.Task, error) {
   738  					return cluster.AddHost(ctx, spec, true, nil, nil)
   739  				})
   740  				if err != nil {
   741  					return err
   742  				}
   743  			}
   744  
   745  			rootRP, err := cluster.ResourcePool(ctx)
   746  			if err != nil {
   747  				return err
   748  			}
   749  
   750  			prefix := clusterName + "_RP"
   751  
   752  			// put VMs in cluster RP if no child RP(s) configured
   753  			if m.Pool == 0 {
   754  				addMachine(prefix+"0", nil, rootRP, folders)
   755  			}
   756  
   757  			// create child RP(s) with VMs
   758  			for childRP := 1; childRP <= m.Pool; childRP++ {
   759  				spec := types.DefaultResourceConfigSpec()
   760  
   761  				p, err := rootRP.Create(ctx, m.fmtName(prefix, childRP), spec)
   762  				addMachine(m.fmtName(prefix, childRP), nil, p, folders)
   763  				if err != nil {
   764  					return err
   765  				}
   766  			}
   767  
   768  			prefix = clusterName + "_APP"
   769  
   770  			for napp := 0; napp < m.App; napp++ {
   771  				rspec := types.DefaultResourceConfigSpec()
   772  				vspec := NewVAppConfigSpec()
   773  				name := m.fmtName(prefix, napp)
   774  
   775  				vapp, err := rootRP.CreateVApp(ctx, name, rspec, vspec, nil)
   776  				if err != nil {
   777  					return err
   778  				}
   779  
   780  				addMachine(name, nil, vapp.ResourcePool, folders)
   781  			}
   782  		}
   783  
   784  		hostMap[dcName] = hosts
   785  		hosts = nil
   786  	}
   787  
   788  	if m.ServiceContent.RootFolder == esx.RootFolder.Reference() {
   789  		// ESX model
   790  		host := object.NewHostSystem(client, esx.HostSystem.Reference())
   791  
   792  		dc := object.NewDatacenter(client, esx.Datacenter.Reference())
   793  		folders, err := dc.Folders(ctx)
   794  		if err != nil {
   795  			return err
   796  		}
   797  
   798  		hostMap[dc.Reference().Value] = append(hosts, host)
   799  
   800  		addMachine(host.Reference().Value, host, nil, folders)
   801  	}
   802  
   803  	for dc, dchosts := range hostMap {
   804  		for i := 0; i < m.Datastore; i++ {
   805  			err := m.createLocalDatastore(dc, m.fmtName("LocalDS_", i), dchosts)
   806  			if err != nil {
   807  				return err
   808  			}
   809  		}
   810  	}
   811  
   812  	for _, createVM := range vms {
   813  		err := createVM()
   814  		if err != nil {
   815  			return err
   816  		}
   817  	}
   818  
   819  	// Turn on delay AFTER we're done building the service content
   820  	m.Service.delay = &m.DelayConfig
   821  
   822  	return nil
   823  }
   824  
   825  func (m *Model) createTempDir(dc string, name string) (string, error) {
   826  	dir, err := os.MkdirTemp("", fmt.Sprintf("govcsim-%s-%s-", dc, name))
   827  	if err == nil {
   828  		m.dirs = append(m.dirs, dir)
   829  	}
   830  	return dir, err
   831  }
   832  
   833  func (m *Model) createLocalDatastore(dc string, name string, hosts []*object.HostSystem) error {
   834  	ctx := context.Background()
   835  	dir, err := m.createTempDir(dc, name)
   836  	if err != nil {
   837  		return err
   838  	}
   839  
   840  	for _, host := range hosts {
   841  		dss, err := host.ConfigManager().DatastoreSystem(ctx)
   842  		if err != nil {
   843  			return err
   844  		}
   845  
   846  		_, err = dss.CreateLocalDatastore(ctx, name, dir)
   847  		if err != nil {
   848  			return err
   849  		}
   850  	}
   851  
   852  	return nil
   853  }
   854  
   855  // Remove cleans up items created by the Model, such as local datastore directories
   856  func (m *Model) Remove() {
   857  	// Remove associated vm containers, if any
   858  	Map.m.Lock()
   859  	for _, obj := range Map.objects {
   860  		if vm, ok := obj.(*VirtualMachine); ok {
   861  			vm.svm.remove(SpoofContext())
   862  		}
   863  	}
   864  	Map.m.Unlock()
   865  
   866  	for _, dir := range m.dirs {
   867  		_ = os.RemoveAll(dir)
   868  	}
   869  }
   870  
   871  // Run calls f with a Client connected to a simulator server instance, which is stopped after f returns.
   872  func (m *Model) Run(f func(context.Context, *vim25.Client) error) error {
   873  	ctx := context.Background()
   874  
   875  	defer m.Remove()
   876  
   877  	if m.Service == nil {
   878  		err := m.Create()
   879  		if err != nil {
   880  			return err
   881  		}
   882  		// Only force TLS if the provided model didn't have any Service.
   883  		m.Service.TLS = new(tls.Config)
   884  	}
   885  
   886  	m.Service.RegisterEndpoints = true
   887  
   888  	s := m.Service.NewServer()
   889  	defer s.Close()
   890  
   891  	c, err := govmomi.NewClient(ctx, s.URL, true)
   892  	if err != nil {
   893  		return err
   894  	}
   895  
   896  	defer c.Logout(ctx)
   897  
   898  	return f(ctx, c.Client)
   899  }
   900  
   901  // Run calls Model.Run for each model and will panic if f returns an error.
   902  // If no model is specified, the VPX Model is used by default.
   903  func Run(f func(context.Context, *vim25.Client) error, model ...*Model) {
   904  	m := model
   905  	if len(m) == 0 {
   906  		m = []*Model{VPX()}
   907  	}
   908  
   909  	for i := range m {
   910  		err := m[i].Run(f)
   911  		if err != nil {
   912  			panic(err)
   913  		}
   914  	}
   915  }
   916  
   917  // Test calls Run and expects the caller propagate any errors, via testing.T for example.
   918  func Test(f func(context.Context, *vim25.Client), model ...*Model) {
   919  	Run(func(ctx context.Context, c *vim25.Client) error {
   920  		f(ctx, c)
   921  		return nil
   922  	}, model...)
   923  }
   924  
   925  // RunContainer runs a vm container with the given args
   926  func RunContainer(ctx context.Context, c *vim25.Client, vm mo.Reference, args string) error {
   927  	obj, ok := vm.(*object.VirtualMachine)
   928  	if !ok {
   929  		obj = object.NewVirtualMachine(c, vm.Reference())
   930  	}
   931  
   932  	task, err := obj.PowerOff(ctx)
   933  	if err != nil {
   934  		return err
   935  	}
   936  	_ = task.Wait(ctx) // ignore InvalidPowerState if already off
   937  
   938  	task, err = obj.Reconfigure(ctx, types.VirtualMachineConfigSpec{
   939  		ExtraConfig: []types.BaseOptionValue{&types.OptionValue{Key: "RUN.container", Value: args}},
   940  	})
   941  	if err != nil {
   942  		return err
   943  	}
   944  	if err = task.Wait(ctx); err != nil {
   945  		return err
   946  	}
   947  
   948  	task, err = obj.PowerOn(ctx)
   949  	if err != nil {
   950  		return err
   951  	}
   952  	return task.Wait(ctx)
   953  }
   954  
   955  // delay sleeps according to DelayConfig. If no delay specified, returns immediately.
   956  func (dc *DelayConfig) delay(method string) {
   957  	d := 0
   958  	if dc.Delay > 0 {
   959  		d = dc.Delay
   960  	}
   961  	if md, ok := dc.MethodDelay[method]; ok {
   962  		d += md
   963  	}
   964  	if dc.DelayJitter > 0 {
   965  		d += int(rand.NormFloat64() * dc.DelayJitter * float64(d))
   966  	}
   967  	if d > 0 {
   968  		//fmt.Printf("Delaying method %s %d ms\n", method, d)
   969  		time.Sleep(time.Duration(d) * time.Millisecond)
   970  	}
   971  }