github.com/vmware/govmomi@v0.37.1/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 }