1/* 2Copyright (c) 2017 VMware, Inc. All Rights Reserved. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package simulator 18 19import ( 20 "context" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "path" 25 26 "github.com/vmware/govmomi/object" 27 "github.com/vmware/govmomi/simulator/esx" 28 "github.com/vmware/govmomi/simulator/vpx" 29 "github.com/vmware/govmomi/vim25/mo" 30 "github.com/vmware/govmomi/vim25/types" 31) 32 33type DelayConfig struct { 34 // Delay specifies the number of milliseconds to delay serving a SOAP call. 0 means no delay. 35 // This can be used to simulate a poorly performing vCenter or network lag. 36 Delay int 37 38 // Delay specifies the number of milliseconds to delay serving a specific method. 39 // Each entry in the map represents the name of a method and its associated delay in milliseconds, 40 // This can be used to simulate a poorly performing vCenter or network lag. 41 MethodDelay map[string]int 42 43 // DelayJitter defines the delay jitter as a coefficient of variation (stddev/mean). 44 // This can be used to simulate unpredictable delay. 0 means no jitter, i.e. all invocations get the same delay. 45 DelayJitter float64 46} 47 48// Model is used to populate a Model with an initial set of managed entities. 49// This is a simple helper for tests running against a simulator, to populate an inventory 50// with commonly used models. 51type Model struct { 52 Service *Service `json:"-"` 53 54 ServiceContent types.ServiceContent `json:"-"` 55 RootFolder mo.Folder `json:"-"` 56 57 // Autostart will power on Model created VMs when true 58 Autostart bool `json:"-"` 59 60 // Datacenter specifies the number of Datacenter entities to create 61 Datacenter int 62 63 // Portgroup specifies the number of DistributedVirtualPortgroup entities to create per Datacenter 64 Portgroup int 65 66 // Host specifies the number of standalone HostSystems entities to create per Datacenter 67 Host int `json:",omitempty"` 68 69 // Cluster specifies the number of ClusterComputeResource entities to create per Datacenter 70 Cluster int 71 72 // ClusterHost specifies the number of HostSystems entities to create within a Cluster 73 ClusterHost int `json:",omitempty"` 74 75 // Pool specifies the number of ResourcePool entities to create per Cluster 76 Pool int 77 78 // Datastore specifies the number of Datastore entities to create 79 // Each Datastore will have temporary local file storage and will be mounted 80 // on every HostSystem created by the ModelConfig 81 Datastore int 82 83 // Machine specifies the number of VirtualMachine entities to create per ResourcePool 84 Machine int 85 86 // Folder specifies the number of Datacenter to place within a Folder. 87 // This includes a folder for the Datacenter itself and its host, vm, network and datastore folders. 88 // All resources for the Datacenter are placed within these folders, rather than the top-level folders. 89 Folder int 90 91 // App specifies the number of VirtualApp to create per Cluster 92 App int 93 94 // Pod specifies the number of StoragePod to create per Cluster 95 Pod int 96 97 // Delay configurations 98 DelayConfig DelayConfig 99 100 // total number of inventory objects, set by Count() 101 total int 102 103 dirs []string 104} 105 106// ESX is the default Model for a standalone ESX instance 107func ESX() *Model { 108 return &Model{ 109 ServiceContent: esx.ServiceContent, 110 RootFolder: esx.RootFolder, 111 Autostart: true, 112 Datastore: 1, 113 Machine: 2, 114 DelayConfig: DelayConfig{ 115 Delay: 0, 116 DelayJitter: 0, 117 MethodDelay: nil, 118 }, 119 } 120} 121 122// VPX is the default Model for a vCenter instance 123func VPX() *Model { 124 return &Model{ 125 ServiceContent: vpx.ServiceContent, 126 RootFolder: vpx.RootFolder, 127 Autostart: true, 128 Datacenter: 1, 129 Portgroup: 1, 130 Host: 1, 131 Cluster: 1, 132 ClusterHost: 3, 133 Datastore: 1, 134 Machine: 2, 135 DelayConfig: DelayConfig{ 136 Delay: 0, 137 DelayJitter: 0, 138 MethodDelay: nil, 139 }, 140 } 141} 142 143// Count returns a Model with total number of each existing type 144func (m *Model) Count() Model { 145 count := Model{} 146 147 for ref, obj := range Map.objects { 148 if _, ok := obj.(mo.Entity); !ok { 149 continue 150 } 151 152 count.total++ 153 154 switch ref.Type { 155 case "Datacenter": 156 count.Datacenter++ 157 case "DistributedVirtualPortgroup": 158 count.Portgroup++ 159 case "ClusterComputeResource": 160 count.Cluster++ 161 case "Datastore": 162 count.Datastore++ 163 case "HostSystem": 164 count.Host++ 165 case "VirtualMachine": 166 count.Machine++ 167 case "ResourcePool": 168 count.Pool++ 169 case "VirtualApp": 170 count.App++ 171 case "Folder": 172 count.Folder++ 173 case "StoragePod": 174 count.Pod++ 175 } 176 } 177 178 return count 179} 180 181func (*Model) fmtName(prefix string, num int) string { 182 return fmt.Sprintf("%s%d", prefix, num) 183} 184 185// Create populates the Model with the given ModelConfig 186func (m *Model) Create() error { 187 m.Service = New(NewServiceInstance(m.ServiceContent, m.RootFolder)) 188 189 ctx := context.Background() 190 client := m.Service.client 191 root := object.NewRootFolder(client) 192 193 // After all hosts are created, this var is used to mount the host datastores. 194 var hosts []*object.HostSystem 195 hostMap := make(map[string][]*object.HostSystem) 196 197 // We need to defer VM creation until after the datastores are created. 198 var vms []func() error 199 // 1 DVS per DC, added to all hosts 200 var dvs *object.DistributedVirtualSwitch 201 // 1 NIC per VM, backed by a DVPG if Model.Portgroup > 0 202 vmnet := esx.EthernetCard.Backing 203 204 // addHost adds a cluster host or a stanalone host. 205 addHost := func(name string, f func(types.HostConnectSpec) (*object.Task, error)) (*object.HostSystem, error) { 206 spec := types.HostConnectSpec{ 207 HostName: name, 208 } 209 210 task, err := f(spec) 211 if err != nil { 212 return nil, err 213 } 214 215 info, err := task.WaitForResult(context.Background(), nil) 216 if err != nil { 217 return nil, err 218 } 219 220 host := object.NewHostSystem(client, info.Result.(types.ManagedObjectReference)) 221 hosts = append(hosts, host) 222 223 if dvs != nil { 224 config := &types.DVSConfigSpec{ 225 Host: []types.DistributedVirtualSwitchHostMemberConfigSpec{{ 226 Operation: string(types.ConfigSpecOperationAdd), 227 Host: host.Reference(), 228 }}, 229 } 230 231 _, _ = dvs.Reconfigure(ctx, config) 232 } 233 234 return host, nil 235 } 236 237 // addMachine returns a func to create a VM. 238 addMachine := func(prefix string, host *object.HostSystem, pool *object.ResourcePool, folders *object.DatacenterFolders) { 239 nic := esx.EthernetCard 240 nic.Backing = vmnet 241 ds := types.ManagedObjectReference{} 242 243 f := func() error { 244 for i := 0; i < m.Machine; i++ { 245 name := m.fmtName(prefix+"_VM", i) 246 247 config := types.VirtualMachineConfigSpec{ 248 Name: name, 249 GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest), 250 Files: &types.VirtualMachineFileInfo{ 251 VmPathName: "[LocalDS_0]", 252 }, 253 } 254 255 if pool == nil { 256 pool, _ = host.ResourcePool(ctx) 257 } 258 259 var devices object.VirtualDeviceList 260 261 scsi, _ := devices.CreateSCSIController("pvscsi") 262 ide, _ := devices.CreateIDEController() 263 cdrom, _ := devices.CreateCdrom(ide.(*types.VirtualIDEController)) 264 disk := devices.CreateDisk(scsi.(types.BaseVirtualController), ds, 265 config.Files.VmPathName+" "+path.Join(name, "disk1.vmdk")) 266 disk.CapacityInKB = 1024 267 268 devices = append(devices, scsi, cdrom, disk, &nic) 269 270 config.DeviceChange, _ = devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd) 271 272 task, err := folders.VmFolder.CreateVM(ctx, config, pool, host) 273 if err != nil { 274 return err 275 } 276 277 info, err := task.WaitForResult(ctx, nil) 278 if err != nil { 279 return err 280 } 281 282 vm := object.NewVirtualMachine(client, info.Result.(types.ManagedObjectReference)) 283 284 if m.Autostart { 285 _, _ = vm.PowerOn(ctx) 286 } 287 } 288 289 return nil 290 } 291 292 vms = append(vms, f) 293 } 294 295 nfolder := 0 296 297 for ndc := 0; ndc < m.Datacenter; ndc++ { 298 dcName := m.fmtName("DC", ndc) 299 folder := root 300 fName := m.fmtName("F", nfolder) 301 302 // If Datacenter > Folder, don't create folders for the first N DCs. 303 if nfolder < m.Folder && ndc >= (m.Datacenter-m.Folder) { 304 f, err := folder.CreateFolder(ctx, fName) 305 if err != nil { 306 return err 307 } 308 folder = f 309 } 310 311 dc, err := folder.CreateDatacenter(ctx, dcName) 312 if err != nil { 313 return err 314 } 315 316 folders, err := dc.Folders(ctx) 317 if err != nil { 318 return err 319 } 320 321 if m.Pod > 0 { 322 for pod := 0; pod < m.Pod; pod++ { 323 _, _ = folders.DatastoreFolder.CreateStoragePod(ctx, m.fmtName(dcName+"_POD", pod)) 324 } 325 } 326 327 if folder != root { 328 // Create sub-folders and use them to create any resources that follow 329 subs := []**object.Folder{&folders.DatastoreFolder, &folders.HostFolder, &folders.NetworkFolder, &folders.VmFolder} 330 331 for _, sub := range subs { 332 f, err := (*sub).CreateFolder(ctx, fName) 333 if err != nil { 334 return err 335 } 336 337 *sub = f 338 } 339 340 nfolder++ 341 } 342 343 if m.Portgroup > 0 { 344 var spec types.DVSCreateSpec 345 spec.ConfigSpec = &types.VMwareDVSConfigSpec{} 346 spec.ConfigSpec.GetDVSConfigSpec().Name = m.fmtName("DVS", 0) 347 348 task, err := folders.NetworkFolder.CreateDVS(ctx, spec) 349 if err != nil { 350 return err 351 } 352 353 info, err := task.WaitForResult(ctx, nil) 354 if err != nil { 355 return err 356 } 357 358 dvs = object.NewDistributedVirtualSwitch(client, info.Result.(types.ManagedObjectReference)) 359 360 for npg := 0; npg < m.Portgroup; npg++ { 361 name := m.fmtName(dcName+"_DVPG", npg) 362 363 task, err = dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{{Name: name}}) 364 if err != nil { 365 return err 366 } 367 368 err = task.Wait(ctx) 369 if err != nil { 370 return err 371 } 372 373 // Use the 1st DVPG for the VMs eth0 backing 374 if npg == 0 { 375 // AddPortgroup_Task does not return the moid, so we look it up by name 376 net := Map.Get(folders.NetworkFolder.Reference()).(*Folder) 377 pg := Map.FindByName(name, net.ChildEntity) 378 379 vmnet, _ = object.NewDistributedVirtualPortgroup(client, pg.Reference()).EthernetCardBackingInfo(ctx) 380 } 381 } 382 } 383 384 for nhost := 0; nhost < m.Host; nhost++ { 385 name := m.fmtName(dcName+"_H", nhost) 386 387 host, err := addHost(name, func(spec types.HostConnectSpec) (*object.Task, error) { 388 return folders.HostFolder.AddStandaloneHost(ctx, spec, true, nil, nil) 389 }) 390 if err != nil { 391 return err 392 } 393 394 addMachine(name, host, nil, folders) 395 } 396 397 for ncluster := 0; ncluster < m.Cluster; ncluster++ { 398 clusterName := m.fmtName(dcName+"_C", ncluster) 399 400 cluster, err := folders.HostFolder.CreateCluster(ctx, clusterName, types.ClusterConfigSpecEx{}) 401 if err != nil { 402 return err 403 } 404 405 for nhost := 0; nhost < m.ClusterHost; nhost++ { 406 name := m.fmtName(clusterName+"_H", nhost) 407 408 _, err = addHost(name, func(spec types.HostConnectSpec) (*object.Task, error) { 409 return cluster.AddHost(ctx, spec, true, nil, nil) 410 }) 411 if err != nil { 412 return err 413 } 414 } 415 416 pool, err := cluster.ResourcePool(ctx) 417 if err != nil { 418 return err 419 } 420 421 prefix := clusterName + "_RP" 422 423 addMachine(prefix+"0", nil, pool, folders) 424 425 for npool := 1; npool <= m.Pool; npool++ { 426 spec := types.DefaultResourceConfigSpec() 427 428 _, err = pool.Create(ctx, m.fmtName(prefix, npool), spec) 429 if err != nil { 430 return err 431 } 432 } 433 434 prefix = clusterName + "_APP" 435 436 for napp := 0; napp < m.App; napp++ { 437 rspec := types.DefaultResourceConfigSpec() 438 vspec := NewVAppConfigSpec() 439 name := m.fmtName(prefix, napp) 440 441 vapp, err := pool.CreateVApp(ctx, name, rspec, vspec, nil) 442 if err != nil { 443 return err 444 } 445 446 addMachine(name, nil, vapp.ResourcePool, folders) 447 } 448 } 449 450 hostMap[dcName] = hosts 451 hosts = nil 452 } 453 454 if m.ServiceContent.RootFolder == esx.RootFolder.Reference() { 455 // ESX model 456 host := object.NewHostSystem(client, esx.HostSystem.Reference()) 457 458 dc := object.NewDatacenter(client, esx.Datacenter.Reference()) 459 folders, err := dc.Folders(ctx) 460 if err != nil { 461 return err 462 } 463 464 hostMap[dc.Reference().Value] = append(hosts, host) 465 466 addMachine(host.Reference().Value, host, nil, folders) 467 } 468 469 for dc, dchosts := range hostMap { 470 for i := 0; i < m.Datastore; i++ { 471 err := m.createLocalDatastore(dc, m.fmtName("LocalDS_", i), dchosts) 472 if err != nil { 473 return err 474 } 475 } 476 } 477 478 for _, createVM := range vms { 479 err := createVM() 480 if err != nil { 481 return err 482 } 483 } 484 485 // Turn on delay AFTER we're done building the service content 486 m.Service.delay = &m.DelayConfig 487 488 return nil 489} 490 491func (m *Model) createLocalDatastore(dc string, name string, hosts []*object.HostSystem) error { 492 ctx := context.Background() 493 dir, err := ioutil.TempDir("", fmt.Sprintf("govcsim-%s-%s-", dc, name)) 494 if err != nil { 495 return err 496 } 497 498 m.dirs = append(m.dirs, dir) 499 500 for _, host := range hosts { 501 dss, err := host.ConfigManager().DatastoreSystem(ctx) 502 if err != nil { 503 return err 504 } 505 506 _, err = dss.CreateLocalDatastore(ctx, name, dir) 507 if err != nil { 508 return err 509 } 510 } 511 512 return nil 513} 514 515// Remove cleans up items created by the Model, such as local datastore directories 516func (m *Model) Remove() { 517 for _, dir := range m.dirs { 518 _ = os.RemoveAll(dir) 519 } 520} 521