github.com/vmware/govmomi@v0.37.1/simulator/host_system.go (about) 1 /* 2 Copyright (c) 2017 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 "fmt" 21 "net" 22 "os" 23 "sync" 24 "time" 25 26 "github.com/vmware/govmomi/simulator/esx" 27 "github.com/vmware/govmomi/vim25/methods" 28 "github.com/vmware/govmomi/vim25/mo" 29 "github.com/vmware/govmomi/vim25/soap" 30 "github.com/vmware/govmomi/vim25/types" 31 ) 32 33 var ( 34 hostPortUnique = os.Getenv("VCSIM_HOST_PORT_UNIQUE") == "true" 35 36 globalLock sync.Mutex 37 // globalHostCount is used to construct unique hostnames. Should be consumed under globalLock. 38 globalHostCount = 0 39 ) 40 41 type HostSystem struct { 42 mo.HostSystem 43 44 sh *simHost 45 } 46 47 func asHostSystemMO(obj mo.Reference) (*mo.HostSystem, bool) { 48 h, ok := getManagedObject(obj).Addr().Interface().(*mo.HostSystem) 49 return h, ok 50 } 51 52 func NewHostSystem(host mo.HostSystem) *HostSystem { 53 if hostPortUnique { // configure unique port for each host 54 port := &esx.HostSystem.Summary.Config.Port 55 *port++ 56 host.Summary.Config.Port = *port 57 } 58 59 now := time.Now() 60 61 hs := &HostSystem{ 62 HostSystem: host, 63 } 64 65 hs.Name = hs.Summary.Config.Name 66 hs.Summary.Runtime = &hs.Runtime 67 hs.Summary.Runtime.BootTime = &now 68 69 // shallow copy Summary.Hardware, as each host will be assigned its own .Uuid 70 hardware := *host.Summary.Hardware 71 hs.Summary.Hardware = &hardware 72 73 if hs.Hardware == nil { 74 // shallow copy Hardware, as each host will be assigned its own .Uuid 75 info := *esx.HostHardwareInfo 76 hs.Hardware = &info 77 } 78 79 cfg := new(types.HostConfigInfo) 80 deepCopy(hs.Config, cfg) 81 hs.Config = cfg 82 83 // copy over the reference advanced options so each host can have it's own, allowing hosts to be configured for 84 // container backing individually 85 deepCopy(esx.AdvancedOptions, &cfg.Option) 86 87 // add a supported option to the AdvancedOption manager 88 simOption := types.OptionDef{ElementDescription: types.ElementDescription{Key: advOptContainerBackingImage}} 89 // TODO: how do we enter patterns here? Or should we stick to a list in the value? 90 // patterns become necessary if we want to enforce correctness on options for RUN.underlay.<pnic> or allow RUN.port.xxx 91 hs.Config.OptionDef = append(hs.Config.OptionDef, simOption) 92 93 config := []struct { 94 ref **types.ManagedObjectReference 95 obj mo.Reference 96 }{ 97 {&hs.ConfigManager.DatastoreSystem, &HostDatastoreSystem{Host: &hs.HostSystem}}, 98 {&hs.ConfigManager.NetworkSystem, NewHostNetworkSystem(&hs.HostSystem)}, 99 {&hs.ConfigManager.AdvancedOption, NewOptionManager(nil, nil, &hs.Config.Option)}, 100 {&hs.ConfigManager.FirewallSystem, NewHostFirewallSystem(&hs.HostSystem)}, 101 {&hs.ConfigManager.StorageSystem, NewHostStorageSystem(&hs.HostSystem)}, 102 } 103 104 for _, c := range config { 105 ref := Map.Put(c.obj).Reference() 106 107 *c.ref = &ref 108 } 109 110 return hs 111 } 112 113 func (h *HostSystem) configure(ctx *Context, spec types.HostConnectSpec, connected bool) { 114 h.Runtime.ConnectionState = types.HostSystemConnectionStateDisconnected 115 if connected { 116 h.Runtime.ConnectionState = types.HostSystemConnectionStateConnected 117 } 118 119 // lets us construct non-conflicting hostname automatically if omitted 120 // does not use the unique port instead to avoid constraints on port, such as >1024 121 122 globalLock.Lock() 123 instanceID := globalHostCount 124 globalHostCount++ 125 globalLock.Unlock() 126 127 if spec.HostName == "" { 128 spec.HostName = fmt.Sprintf("esx-%d", instanceID) 129 } else if net.ParseIP(spec.HostName) != nil { 130 h.Config.Network.Vnic[0].Spec.Ip.IpAddress = spec.HostName 131 } 132 133 h.Summary.Config.Name = spec.HostName 134 h.Name = h.Summary.Config.Name 135 id := newUUID(h.Name) 136 h.Summary.Hardware.Uuid = id 137 h.Hardware.SystemInfo.Uuid = id 138 139 var err error 140 h.sh, err = createSimulationHost(ctx, h) 141 if err != nil { 142 panic("failed to create simulation host and no path to return error: " + err.Error()) 143 } 144 } 145 146 // configureContainerBacking sets up _this_ host for simulation using a container backing. 147 // Args: 148 // 149 // image - the container image with which to simulate the host 150 // mounts - array of mount info that should be translated into /vmfs/volumes/... mounts backed by container volumes 151 // networks - names of bridges to use for underlays. Will create a pNIC for each. The first will be treated as the management network. 152 // 153 // Restrictions adopted from createSimulationHost: 154 // * no mock of VLAN connectivity 155 // * only a single vmknic, used for "the management IP" 156 // * pNIC connectivity does not directly impact VMs/vmks using it as uplink 157 // 158 // The pnics will be named using standard pattern, ie. vmnic0, vmnic1, ... 159 // This will sanity check the NetConfig for "management" nicType to ensure that it maps through PortGroup->vSwitch->pNIC to vmnic0. 160 func (h *HostSystem) configureContainerBacking(ctx *Context, image string, mounts []types.HostFileSystemMountInfo, networks ...string) error { 161 option := &types.OptionValue{ 162 Key: advOptContainerBackingImage, 163 Value: image, 164 } 165 166 advOpts := ctx.Map.Get(h.ConfigManager.AdvancedOption.Reference()).(*OptionManager) 167 fault := advOpts.UpdateOptions(&types.UpdateOptions{ChangedValue: []types.BaseOptionValue{option}}).Fault() 168 if fault != nil { 169 panic(fault) 170 } 171 172 h.Config.FileSystemVolume = nil 173 if mounts != nil { 174 h.Config.FileSystemVolume = &types.HostFileSystemVolumeInfo{ 175 VolumeTypeList: []string{"VMFS", "OTHER"}, 176 MountInfo: mounts, 177 } 178 } 179 180 // force at least a management network 181 if len(networks) == 0 { 182 networks = []string{defaultUnderlayBridgeName} 183 } 184 185 // purge pNICs from the template - it makes no sense to keep them for a sim host 186 h.Config.Network.Pnic = make([]types.PhysicalNic, len(networks)) 187 188 // purge any IPs and MACs associated with existing NetConfigs for the host 189 for cfgIdx := range h.Config.VirtualNicManagerInfo.NetConfig { 190 config := &h.Config.VirtualNicManagerInfo.NetConfig[cfgIdx] 191 for candidateIdx := range config.CandidateVnic { 192 candidate := &config.CandidateVnic[candidateIdx] 193 candidate.Spec.Ip.IpAddress = "0.0.0.0" 194 candidate.Spec.Ip.SubnetMask = "0.0.0.0" 195 candidate.Spec.Mac = "00:00:00:00:00:00" 196 } 197 } 198 199 // The presence of a pNIC is used to indicate connectivity to a specific underlay. We construct an empty pNIC entry and specify the underly via 200 // host.ConfigManager.AdvancedOptions. The pNIC will be populated with the MAC (accurate) and IP (divergence - we need to stash it somewhere) for the veth. 201 // We create a NetConfig "management" entry for the first pNIC - this will be populated with the IP of the "host" container. 202 203 // create a pNIC for each underlay 204 for i, net := range networks { 205 name := fmt.Sprintf("vmnic%d", i) 206 207 // we don't have a natural field for annotating which pNIC is connected to which network, so stash it in an adv option. 208 option := &types.OptionValue{ 209 Key: advOptPrefixPnicToUnderlayPrefix + name, 210 Value: net, 211 } 212 fault = advOpts.UpdateOptions(&types.UpdateOptions{ChangedValue: []types.BaseOptionValue{option}}).Fault() 213 if fault != nil { 214 panic(fault) 215 } 216 217 h.Config.Network.Pnic[i] = types.PhysicalNic{ 218 Key: "key-vim.host.PhysicalNic-" + name, 219 Device: name, 220 Pci: fmt.Sprintf("0000:%2d:00.0", i+1), 221 Driver: "vcsim-bridge", 222 DriverVersion: "1.2.10.0", 223 FirmwareVersion: "1.57, 0x80000185", 224 LinkSpeed: &types.PhysicalNicLinkInfo{ 225 SpeedMb: 10000, 226 Duplex: true, 227 }, 228 ValidLinkSpecification: []types.PhysicalNicLinkInfo{ 229 { 230 SpeedMb: 10000, 231 Duplex: true, 232 }, 233 }, 234 Spec: types.PhysicalNicSpec{ 235 Ip: &types.HostIpConfig{}, 236 LinkSpeed: (*types.PhysicalNicLinkInfo)(nil), 237 EnableEnhancedNetworkingStack: types.NewBool(false), 238 EnsInterruptEnabled: types.NewBool(false), 239 }, 240 WakeOnLanSupported: false, 241 Mac: "00:00:00:00:00:00", 242 FcoeConfiguration: &types.FcoeConfig{ 243 PriorityClass: 3, 244 SourceMac: "00:00:00:00:00:00", 245 VlanRange: []types.FcoeConfigVlanRange{ 246 {}, 247 }, 248 Capabilities: types.FcoeConfigFcoeCapabilities{}, 249 FcoeActive: false, 250 }, 251 VmDirectPathGen2Supported: types.NewBool(false), 252 VmDirectPathGen2SupportedMode: "", 253 ResourcePoolSchedulerAllowed: types.NewBool(false), 254 ResourcePoolSchedulerDisallowedReason: nil, 255 AutoNegotiateSupported: types.NewBool(true), 256 EnhancedNetworkingStackSupported: types.NewBool(false), 257 EnsInterruptSupported: types.NewBool(false), 258 RdmaDevice: "", 259 DpuId: "", 260 } 261 } 262 263 // sanity check that everything's hung together sufficiently well 264 details, err := h.getNetConfigInterface(ctx, "management") 265 if err != nil { 266 return err 267 } 268 269 if details.uplink == nil || details.uplink.Device != "vmnic0" { 270 return fmt.Errorf("Config provided for host %s does not result in a consistent 'management' NetConfig that's bound to 'vmnic0'", h.Name) 271 } 272 273 return nil 274 } 275 276 // netConfigDetails is used to packaged up all the related network entities associated with a NetConfig binding 277 type netConfigDetails struct { 278 nicType string 279 netconfig *types.VirtualNicManagerNetConfig 280 vmk *types.HostVirtualNic 281 netstack *types.HostNetStackInstance 282 portgroup *types.HostPortGroup 283 vswitch *types.HostVirtualSwitch 284 uplink *types.PhysicalNic 285 } 286 287 // getNetConfigInterface returns the set of constructs active for a given nicType (eg. "management", "vmotion") 288 // This method is provided because the Config structure held by HostSystem is heavily interconnected but serialized and not cross-linked with pointers. 289 // As such there's a _lot_ of cross-referencing that needs to be done to navigate. 290 // The pNIC returned is the uplink associated with the vSwitch for the netconfig 291 func (h *HostSystem) getNetConfigInterface(ctx *Context, nicType string) (*netConfigDetails, error) { 292 details := &netConfigDetails{ 293 nicType: nicType, 294 } 295 296 for i := range h.Config.VirtualNicManagerInfo.NetConfig { 297 if h.Config.VirtualNicManagerInfo.NetConfig[i].NicType == nicType { 298 details.netconfig = &h.Config.VirtualNicManagerInfo.NetConfig[i] 299 break 300 } 301 } 302 if details.netconfig == nil { 303 return nil, fmt.Errorf("no matching NetConfig for NicType=%s", nicType) 304 } 305 306 if details.netconfig.SelectedVnic == nil { 307 return details, nil 308 } 309 310 vnicKey := details.netconfig.SelectedVnic[0] 311 for i := range details.netconfig.CandidateVnic { 312 if details.netconfig.CandidateVnic[i].Key == vnicKey { 313 details.vmk = &details.netconfig.CandidateVnic[i] 314 break 315 } 316 } 317 if details.vmk == nil { 318 panic(fmt.Sprintf("NetConfig for host %s references non-existant vNIC key %s for %s nicType", h.Name, vnicKey, nicType)) 319 } 320 321 portgroupName := details.vmk.Portgroup 322 netstackKey := details.vmk.Spec.NetStackInstanceKey 323 324 for i := range h.Config.Network.NetStackInstance { 325 if h.Config.Network.NetStackInstance[i].Key == netstackKey { 326 details.netstack = &h.Config.Network.NetStackInstance[i] 327 break 328 } 329 } 330 if details.netstack == nil { 331 panic(fmt.Sprintf("NetConfig for host %s references non-existant NetStack key %s for %s nicType", h.Name, netstackKey, nicType)) 332 } 333 334 for i := range h.Config.Network.Portgroup { 335 // TODO: confirm correctness of this - seems weird it references the Spec.Name instead of the key like everything else. 336 if h.Config.Network.Portgroup[i].Spec.Name == portgroupName { 337 details.portgroup = &h.Config.Network.Portgroup[i] 338 break 339 } 340 } 341 if details.portgroup == nil { 342 panic(fmt.Sprintf("NetConfig for host %s references non-existant PortGroup name %s for %s nicType", h.Name, portgroupName, nicType)) 343 } 344 345 vswitchKey := details.portgroup.Vswitch 346 for i := range h.Config.Network.Vswitch { 347 if h.Config.Network.Vswitch[i].Key == vswitchKey { 348 details.vswitch = &h.Config.Network.Vswitch[i] 349 break 350 } 351 } 352 if details.vswitch == nil { 353 panic(fmt.Sprintf("NetConfig for host %s references non-existant vSwitch key %s for %s nicType", h.Name, vswitchKey, nicType)) 354 } 355 356 if len(details.vswitch.Pnic) != 1 { 357 // to change this, look at the Active NIC in the NicTeamingPolicy, but for now not worth it 358 panic(fmt.Sprintf("vSwitch %s for host %s has multiple pNICs associated which is not supported.", vswitchKey, h.Name)) 359 } 360 361 pnicKey := details.vswitch.Pnic[0] 362 for i := range h.Config.Network.Pnic { 363 if h.Config.Network.Pnic[i].Key == pnicKey { 364 details.uplink = &h.Config.Network.Pnic[i] 365 break 366 } 367 } 368 if details.uplink == nil { 369 panic(fmt.Sprintf("NetConfig for host %s references non-existant pNIC key %s for %s nicType", h.Name, pnicKey, nicType)) 370 } 371 372 return details, nil 373 } 374 375 func (h *HostSystem) event() types.HostEvent { 376 return types.HostEvent{ 377 Event: types.Event{ 378 Datacenter: datacenterEventArgument(h), 379 ComputeResource: h.eventArgumentParent(), 380 Host: h.eventArgument(), 381 }, 382 } 383 } 384 385 func (h *HostSystem) eventArgument() *types.HostEventArgument { 386 return &types.HostEventArgument{ 387 Host: h.Self, 388 EntityEventArgument: types.EntityEventArgument{Name: h.Name}, 389 } 390 } 391 392 func (h *HostSystem) eventArgumentParent() *types.ComputeResourceEventArgument { 393 parent := hostParent(&h.HostSystem) 394 395 return &types.ComputeResourceEventArgument{ 396 ComputeResource: parent.Self, 397 EntityEventArgument: types.EntityEventArgument{Name: parent.Name}, 398 } 399 } 400 401 func hostParent(host *mo.HostSystem) *mo.ComputeResource { 402 switch parent := Map.Get(*host.Parent).(type) { 403 case *mo.ComputeResource: 404 return parent 405 case *ClusterComputeResource: 406 return &parent.ComputeResource 407 default: 408 return nil 409 } 410 } 411 412 func addComputeResource(s *types.ComputeResourceSummary, h *HostSystem) { 413 s.TotalCpu += h.Summary.Hardware.CpuMhz 414 s.TotalMemory += h.Summary.Hardware.MemorySize 415 s.NumCpuCores += h.Summary.Hardware.NumCpuCores 416 s.NumCpuThreads += h.Summary.Hardware.NumCpuThreads 417 s.EffectiveCpu += h.Summary.Hardware.CpuMhz 418 s.EffectiveMemory += h.Summary.Hardware.MemorySize 419 s.NumHosts++ 420 s.NumEffectiveHosts++ 421 s.OverallStatus = types.ManagedEntityStatusGreen 422 } 423 424 // CreateDefaultESX creates a standalone ESX 425 // Adds objects of type: Datacenter, Network, ComputeResource, ResourcePool and HostSystem 426 func CreateDefaultESX(ctx *Context, f *Folder) { 427 dc := NewDatacenter(ctx, &f.Folder) 428 429 host := NewHostSystem(esx.HostSystem) 430 431 summary := new(types.ComputeResourceSummary) 432 addComputeResource(summary, host) 433 434 cr := &mo.ComputeResource{ 435 Summary: summary, 436 Network: esx.Datacenter.Network, 437 } 438 cr.Self = *host.Parent 439 cr.Name = host.Name 440 cr.Host = append(cr.Host, host.Reference()) 441 host.Network = cr.Network 442 ctx.Map.PutEntity(cr, host) 443 cr.EnvironmentBrowser = newEnvironmentBrowser(ctx, host.Reference()) 444 445 pool := NewResourcePool() 446 cr.ResourcePool = &pool.Self 447 ctx.Map.PutEntity(cr, pool) 448 pool.Owner = cr.Self 449 450 folderPutChild(ctx, &ctx.Map.Get(dc.HostFolder).(*Folder).Folder, cr) 451 } 452 453 // CreateStandaloneHost uses esx.HostSystem as a template, applying the given spec 454 // and creating the ComputeResource parent and ResourcePool sibling. 455 func CreateStandaloneHost(ctx *Context, f *Folder, spec types.HostConnectSpec) (*HostSystem, types.BaseMethodFault) { 456 if spec.HostName == "" { 457 return nil, &types.NoHost{} 458 } 459 460 template := esx.HostSystem 461 network := ctx.Map.getEntityDatacenter(f).defaultNetwork() 462 463 if p := ctx.Map.FindByName(spec.UserName, f.ChildEntity); p != nil { 464 cr := p.(*mo.ComputeResource) 465 h := ctx.Map.Get(cr.Host[0]) 466 // "clone" an existing host from the inventory 467 template = h.(*HostSystem).HostSystem 468 template.Vm = nil 469 network = cr.Network 470 } 471 472 pool := NewResourcePool() 473 host := NewHostSystem(template) 474 host.configure(ctx, spec, false) 475 476 summary := new(types.ComputeResourceSummary) 477 addComputeResource(summary, host) 478 479 cr := &mo.ComputeResource{ 480 ConfigurationEx: &types.ComputeResourceConfigInfo{ 481 VmSwapPlacement: string(types.VirtualMachineConfigInfoSwapPlacementTypeVmDirectory), 482 }, 483 Summary: summary, 484 } 485 486 ctx.Map.PutEntity(cr, ctx.Map.NewEntity(host)) 487 cr.EnvironmentBrowser = newEnvironmentBrowser(ctx, host.Reference()) 488 489 host.Summary.Host = &host.Self 490 host.Config.Host = host.Self 491 492 ctx.Map.PutEntity(cr, ctx.Map.NewEntity(pool)) 493 494 cr.Name = host.Name 495 cr.Network = network 496 cr.Host = append(cr.Host, host.Reference()) 497 cr.ResourcePool = &pool.Self 498 499 folderPutChild(ctx, &f.Folder, cr) 500 pool.Owner = cr.Self 501 host.Network = cr.Network 502 503 return host, nil 504 } 505 506 func (h *HostSystem) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault { 507 task := CreateTask(h, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) { 508 if len(h.Vm) > 0 { 509 return nil, &types.ResourceInUse{} 510 } 511 512 ctx.postEvent(&types.HostRemovedEvent{HostEvent: h.event()}) 513 514 f := ctx.Map.getEntityParent(h, "Folder").(*Folder) 515 folderRemoveChild(ctx, &f.Folder, h.Reference()) 516 err := h.sh.remove(ctx) 517 518 if err != nil { 519 return nil, &types.RuntimeFault{ 520 MethodFault: types.MethodFault{ 521 FaultCause: &types.LocalizedMethodFault{ 522 Fault: &types.SystemErrorFault{Reason: err.Error()}, 523 LocalizedMessage: err.Error()}}} 524 } 525 526 // TODO: should there be events on lifecycle operations as with VMs? 527 528 return nil, nil 529 }) 530 531 return &methods.Destroy_TaskBody{ 532 Res: &types.Destroy_TaskResponse{ 533 Returnval: task.Run(ctx), 534 }, 535 } 536 } 537 538 func (h *HostSystem) EnterMaintenanceModeTask(ctx *Context, spec *types.EnterMaintenanceMode_Task) soap.HasFault { 539 task := CreateTask(h, "enterMaintenanceMode", func(t *Task) (types.AnyType, types.BaseMethodFault) { 540 h.Runtime.InMaintenanceMode = true 541 return nil, nil 542 }) 543 544 return &methods.EnterMaintenanceMode_TaskBody{ 545 Res: &types.EnterMaintenanceMode_TaskResponse{ 546 Returnval: task.Run(ctx), 547 }, 548 } 549 } 550 551 func (h *HostSystem) ExitMaintenanceModeTask(ctx *Context, spec *types.ExitMaintenanceMode_Task) soap.HasFault { 552 task := CreateTask(h, "exitMaintenanceMode", func(t *Task) (types.AnyType, types.BaseMethodFault) { 553 h.Runtime.InMaintenanceMode = false 554 return nil, nil 555 }) 556 557 return &methods.ExitMaintenanceMode_TaskBody{ 558 Res: &types.ExitMaintenanceMode_TaskResponse{ 559 Returnval: task.Run(ctx), 560 }, 561 } 562 } 563 564 func (h *HostSystem) DisconnectHostTask(ctx *Context, spec *types.DisconnectHost_Task) soap.HasFault { 565 task := CreateTask(h, "disconnectHost", func(t *Task) (types.AnyType, types.BaseMethodFault) { 566 h.Runtime.ConnectionState = types.HostSystemConnectionStateDisconnected 567 return nil, nil 568 }) 569 570 return &methods.DisconnectHost_TaskBody{ 571 Res: &types.DisconnectHost_TaskResponse{ 572 Returnval: task.Run(ctx), 573 }, 574 } 575 } 576 577 func (h *HostSystem) ReconnectHostTask(ctx *Context, spec *types.ReconnectHost_Task) soap.HasFault { 578 task := CreateTask(h, "reconnectHost", func(t *Task) (types.AnyType, types.BaseMethodFault) { 579 h.Runtime.ConnectionState = types.HostSystemConnectionStateConnected 580 return nil, nil 581 }) 582 583 return &methods.ReconnectHost_TaskBody{ 584 Res: &types.ReconnectHost_TaskResponse{ 585 Returnval: task.Run(ctx), 586 }, 587 } 588 }