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 }