github.com/vmware/govmomi@v0.51.0/simulator/model.go (about)

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