github.com/vmware/govmomi@v0.37.1/simulator/folder.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 "errors" 21 "fmt" 22 "math/rand" 23 "net/url" 24 "path" 25 "strings" 26 "time" 27 28 "github.com/google/uuid" 29 30 "github.com/vmware/govmomi/object" 31 "github.com/vmware/govmomi/vim25/methods" 32 "github.com/vmware/govmomi/vim25/mo" 33 "github.com/vmware/govmomi/vim25/soap" 34 "github.com/vmware/govmomi/vim25/types" 35 ) 36 37 type Folder struct { 38 mo.Folder 39 } 40 41 func asFolderMO(obj mo.Reference) (*mo.Folder, bool) { 42 if obj == nil { 43 return nil, false 44 } 45 f, ok := getManagedObject(obj).Addr().Interface().(*mo.Folder) 46 return f, ok 47 } 48 49 func folderEventArgument(f *mo.Folder) types.FolderEventArgument { 50 return types.FolderEventArgument{ 51 Folder: f.Self, 52 EntityEventArgument: types.EntityEventArgument{Name: f.Name}, 53 } 54 } 55 56 // update references when objects are added/removed from a Folder 57 func folderUpdate(ctx *Context, f *mo.Folder, o mo.Reference, u func(*Context, mo.Reference, *[]types.ManagedObjectReference, types.ManagedObjectReference)) { 58 ref := o.Reference() 59 60 if f.Parent == nil { 61 return // this is the root folder 62 } 63 64 switch ref.Type { 65 case "Datacenter", "Folder": 66 return // nothing to update 67 } 68 69 dc := ctx.Map.getEntityDatacenter(f) 70 71 switch ref.Type { 72 case "Network", "DistributedVirtualSwitch", "DistributedVirtualPortgroup": 73 u(ctx, dc, &dc.Network, ref) 74 case "Datastore": 75 u(ctx, dc, &dc.Datastore, ref) 76 } 77 } 78 79 func networkSummary(n *mo.Network) types.BaseNetworkSummary { 80 if n.Summary != nil { 81 return n.Summary 82 } 83 return &types.NetworkSummary{ 84 Network: &n.Self, 85 Name: n.Name, 86 Accessible: true, 87 } 88 } 89 90 func folderPutChild(ctx *Context, f *mo.Folder, o mo.Entity) { 91 ctx.WithLock(f, func() { 92 // Need to update ChildEntity before Map.Put for ContainerView updates to work properly 93 f.ChildEntity = append(f.ChildEntity, ctx.Map.reference(o)) 94 ctx.Map.PutEntity(f, o) 95 96 folderUpdate(ctx, f, o, ctx.Map.AddReference) 97 98 ctx.WithLock(o, func() { 99 switch e := o.(type) { 100 case *mo.Network: 101 e.Summary = networkSummary(e) 102 case *mo.OpaqueNetwork: 103 e.Summary = networkSummary(&e.Network) 104 case *DistributedVirtualPortgroup: 105 e.Summary = networkSummary(&e.Network) 106 } 107 }) 108 }) 109 } 110 111 func folderRemoveChild(ctx *Context, f *mo.Folder, o mo.Reference) { 112 ctx.Map.Remove(ctx, o.Reference()) 113 114 ctx.WithLock(f, func() { 115 RemoveReference(&f.ChildEntity, o.Reference()) 116 117 folderUpdate(ctx, f, o, ctx.Map.RemoveReference) 118 }) 119 } 120 121 func folderHasChildType(f *mo.Folder, kind string) bool { 122 for _, t := range f.ChildType { 123 if t == kind { 124 return true 125 } 126 } 127 return false 128 } 129 130 func (f *Folder) typeNotSupported() *soap.Fault { 131 return Fault(fmt.Sprintf("%s supports types: %#v", f.Self, f.ChildType), &types.NotSupported{}) 132 } 133 134 // AddOpaqueNetwork adds an OpaqueNetwork type to the inventory, with default backing to that of an nsx.LogicalSwitch. 135 // The vSphere API does not have a method to add this directly, so it must either be called directly or via Model.OpaqueNetwork setting. 136 func (f *Folder) AddOpaqueNetwork(ctx *Context, summary types.OpaqueNetworkSummary) error { 137 if !folderHasChildType(&f.Folder, "Network") { 138 return errors.New("not a network folder") 139 } 140 141 if summary.OpaqueNetworkId == "" { 142 summary.OpaqueNetworkId = uuid.New().String() 143 } 144 if summary.OpaqueNetworkType == "" { 145 summary.OpaqueNetworkType = "nsx.LogicalSwitch" 146 } 147 if summary.Name == "" { 148 summary.Name = summary.OpaqueNetworkType + "-" + summary.OpaqueNetworkId 149 } 150 151 net := new(mo.OpaqueNetwork) 152 if summary.Network == nil { 153 summary.Network = &net.Self 154 } else { 155 net.Self = *summary.Network 156 } 157 summary.Accessible = true 158 net.Network.Name = summary.Name 159 net.Summary = &summary 160 161 folderPutChild(ctx, &f.Folder, net) 162 163 return nil 164 } 165 166 type addStandaloneHost struct { 167 *Folder 168 ctx *Context 169 req *types.AddStandaloneHost_Task 170 } 171 172 func (add *addStandaloneHost) Run(task *Task) (types.AnyType, types.BaseMethodFault) { 173 host, err := CreateStandaloneHost(add.ctx, add.Folder, add.req.Spec) 174 if err != nil { 175 return nil, err 176 } 177 178 if add.req.AddConnected { 179 host.Runtime.ConnectionState = types.HostSystemConnectionStateConnected 180 } 181 182 return host.Reference(), nil 183 } 184 185 func (f *Folder) AddStandaloneHostTask(ctx *Context, a *types.AddStandaloneHost_Task) soap.HasFault { 186 r := &methods.AddStandaloneHost_TaskBody{} 187 188 if folderHasChildType(&f.Folder, "ComputeResource") && folderHasChildType(&f.Folder, "Folder") { 189 r.Res = &types.AddStandaloneHost_TaskResponse{ 190 Returnval: NewTask(&addStandaloneHost{f, ctx, a}).Run(ctx), 191 } 192 } else { 193 r.Fault_ = f.typeNotSupported() 194 } 195 196 return r 197 } 198 199 func (f *Folder) CreateFolder(ctx *Context, c *types.CreateFolder) soap.HasFault { 200 r := &methods.CreateFolderBody{} 201 202 if folderHasChildType(&f.Folder, "Folder") { 203 name := escapeSpecialCharacters(c.Name) 204 205 if obj := ctx.Map.FindByName(name, f.ChildEntity); obj != nil { 206 r.Fault_ = Fault("", &types.DuplicateName{ 207 Name: name, 208 Object: f.Self, 209 }) 210 211 return r 212 } 213 214 folder := &Folder{} 215 216 folder.Name = name 217 folder.ChildType = f.ChildType 218 219 folderPutChild(ctx, &f.Folder, folder) 220 221 r.Res = &types.CreateFolderResponse{ 222 Returnval: folder.Self, 223 } 224 } else { 225 r.Fault_ = f.typeNotSupported() 226 } 227 228 return r 229 } 230 231 func escapeSpecialCharacters(name string) string { 232 name = strings.ReplaceAll(name, `%`, strings.ToLower(url.QueryEscape(`%`))) 233 name = strings.ReplaceAll(name, `/`, strings.ToLower(url.QueryEscape(`/`))) 234 name = strings.ReplaceAll(name, `\`, strings.ToLower(url.QueryEscape(`\`))) 235 return name 236 } 237 238 // StoragePod aka "Datastore Cluster" 239 type StoragePod struct { 240 mo.StoragePod 241 } 242 243 func (f *Folder) CreateStoragePod(ctx *Context, c *types.CreateStoragePod) soap.HasFault { 244 r := &methods.CreateStoragePodBody{} 245 246 if folderHasChildType(&f.Folder, "StoragePod") { 247 if obj := ctx.Map.FindByName(c.Name, f.ChildEntity); obj != nil { 248 r.Fault_ = Fault("", &types.DuplicateName{ 249 Name: c.Name, 250 Object: f.Self, 251 }) 252 253 return r 254 } 255 256 pod := &StoragePod{} 257 258 pod.Name = c.Name 259 pod.ChildType = []string{"Datastore"} 260 pod.Summary = new(types.StoragePodSummary) 261 pod.PodStorageDrsEntry = new(types.PodStorageDrsEntry) 262 pod.PodStorageDrsEntry.StorageDrsConfig.PodConfig.Enabled = true 263 264 folderPutChild(ctx, &f.Folder, pod) 265 266 r.Res = &types.CreateStoragePodResponse{ 267 Returnval: pod.Self, 268 } 269 } else { 270 r.Fault_ = f.typeNotSupported() 271 } 272 273 return r 274 } 275 276 func (p *StoragePod) MoveIntoFolderTask(ctx *Context, c *types.MoveIntoFolder_Task) soap.HasFault { 277 task := CreateTask(p, "moveIntoFolder", func(*Task) (types.AnyType, types.BaseMethodFault) { 278 f := &Folder{Folder: p.Folder} 279 id := f.MoveIntoFolderTask(ctx, c).(*methods.MoveIntoFolder_TaskBody).Res.Returnval 280 ftask := ctx.Map.Get(id).(*Task) 281 ftask.Wait() 282 if ftask.Info.Error != nil { 283 return nil, ftask.Info.Error.Fault 284 } 285 p.ChildEntity = append(p.ChildEntity, f.ChildEntity...) 286 return nil, nil 287 }) 288 return &methods.MoveIntoFolder_TaskBody{ 289 Res: &types.MoveIntoFolder_TaskResponse{ 290 Returnval: task.Run(ctx), 291 }, 292 } 293 } 294 295 func (f *Folder) CreateDatacenter(ctx *Context, c *types.CreateDatacenter) soap.HasFault { 296 r := &methods.CreateDatacenterBody{} 297 298 if folderHasChildType(&f.Folder, "Datacenter") && folderHasChildType(&f.Folder, "Folder") { 299 dc := NewDatacenter(ctx, &f.Folder) 300 301 ctx.Map.Update(dc, []types.PropertyChange{ 302 {Name: "name", Val: c.Name}, 303 }) 304 305 r.Res = &types.CreateDatacenterResponse{ 306 Returnval: dc.Self, 307 } 308 309 ctx.postEvent(&types.DatacenterCreatedEvent{ 310 DatacenterEvent: types.DatacenterEvent{ 311 Event: types.Event{ 312 Datacenter: datacenterEventArgument(dc), 313 }, 314 }, 315 Parent: folderEventArgument(&f.Folder), 316 }) 317 } else { 318 r.Fault_ = f.typeNotSupported() 319 } 320 321 return r 322 } 323 324 func (f *Folder) CreateClusterEx(ctx *Context, c *types.CreateClusterEx) soap.HasFault { 325 r := &methods.CreateClusterExBody{} 326 327 if folderHasChildType(&f.Folder, "ComputeResource") && folderHasChildType(&f.Folder, "Folder") { 328 cluster, err := CreateClusterComputeResource(ctx, f, c.Name, c.Spec) 329 if err != nil { 330 r.Fault_ = Fault("", err) 331 return r 332 } 333 334 r.Res = &types.CreateClusterExResponse{ 335 Returnval: cluster.Self, 336 } 337 } else { 338 r.Fault_ = f.typeNotSupported() 339 } 340 341 return r 342 } 343 344 type createVM struct { 345 *Folder 346 347 ctx *Context 348 req *types.CreateVM_Task 349 350 register bool 351 } 352 353 // hostsWithDatastore returns hosts that have access to the given datastore path 354 func hostsWithDatastore(hosts []types.ManagedObjectReference, path string) []types.ManagedObjectReference { 355 attached := hosts[:0] 356 var p object.DatastorePath 357 p.FromString(path) 358 359 for _, host := range hosts { 360 h := Map.Get(host).(*HostSystem) 361 if Map.FindByName(p.Datastore, h.Datastore) != nil { 362 attached = append(attached, host) 363 } 364 } 365 366 return attached 367 } 368 369 func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) { 370 config := &c.req.Config 371 // escape special characters in vm name 372 if config.Name != escapeSpecialCharacters(config.Name) { 373 deepCopy(c.req.Config, config) 374 config.Name = escapeSpecialCharacters(config.Name) 375 } 376 377 vm, err := NewVirtualMachine(c.ctx, c.Folder.Self, &c.req.Config) 378 if err != nil { 379 return nil, err 380 } 381 382 vm.ResourcePool = &c.req.Pool 383 384 if c.req.Host == nil { 385 pool := c.ctx.Map.Get(c.req.Pool).(mo.Entity) 386 cr := c.ctx.Map.getEntityComputeResource(pool) 387 388 c.ctx.WithLock(cr, func() { 389 var hosts []types.ManagedObjectReference 390 switch cr := cr.(type) { 391 case *mo.ComputeResource: 392 hosts = cr.Host 393 case *ClusterComputeResource: 394 hosts = cr.Host 395 } 396 397 hosts = hostsWithDatastore(hosts, c.req.Config.Files.VmPathName) 398 host := hosts[rand.Intn(len(hosts))] 399 vm.Runtime.Host = &host 400 }) 401 } else { 402 vm.Runtime.Host = c.req.Host 403 } 404 405 vm.Guest = &types.GuestInfo{ 406 ToolsStatus: types.VirtualMachineToolsStatusToolsNotInstalled, 407 ToolsVersion: "0", 408 ToolsRunningStatus: string(types.VirtualMachineToolsRunningStatusGuestToolsNotRunning), 409 } 410 411 vm.Summary.Guest = &types.VirtualMachineGuestSummary{ 412 ToolsStatus: vm.Guest.ToolsStatus, 413 } 414 vm.Summary.Config.VmPathName = vm.Config.Files.VmPathName 415 vm.Summary.Runtime.Host = vm.Runtime.Host 416 417 err = vm.create(c.ctx, &c.req.Config, c.register) 418 if err != nil { 419 folderRemoveChild(c.ctx, &c.Folder.Folder, vm) 420 return nil, err 421 } 422 423 host := c.ctx.Map.Get(*vm.Runtime.Host).(*HostSystem) 424 c.ctx.Map.AppendReference(c.ctx, host, &host.Vm, vm.Self) 425 vm.EnvironmentBrowser = *hostParent(&host.HostSystem).EnvironmentBrowser 426 427 for i := range vm.Datastore { 428 ds := c.ctx.Map.Get(vm.Datastore[i]).(*Datastore) 429 c.ctx.Map.AppendReference(c.ctx, ds, &ds.Vm, vm.Self) 430 } 431 432 pool := c.ctx.Map.Get(*vm.ResourcePool) 433 // This can be an internal call from VirtualApp.CreateChildVMTask, where pool is already locked. 434 c.ctx.WithLock(pool, func() { 435 if rp, ok := asResourcePoolMO(pool); ok { 436 rp.Vm = append(rp.Vm, vm.Self) 437 } 438 if vapp, ok := pool.(*VirtualApp); ok { 439 vapp.Vm = append(vapp.Vm, vm.Self) 440 } 441 }) 442 443 event := vm.event() 444 c.ctx.postEvent( 445 &types.VmBeingCreatedEvent{ 446 VmEvent: event, 447 ConfigSpec: &c.req.Config, 448 }, 449 &types.VmInstanceUuidAssignedEvent{ 450 VmEvent: event, 451 InstanceUuid: vm.Config.InstanceUuid, 452 }, 453 &types.VmUuidAssignedEvent{ 454 VmEvent: event, 455 Uuid: vm.Config.Uuid, 456 }, 457 &types.VmCreatedEvent{ 458 VmEvent: event, 459 }, 460 ) 461 462 vm.RefreshStorageInfo(c.ctx, nil) 463 464 c.ctx.Map.Update(vm, []types.PropertyChange{ 465 {Name: "name", Val: c.req.Config.Name}, 466 }) 467 468 return vm.Reference(), nil 469 } 470 471 func (f *Folder) CreateVMTask(ctx *Context, c *types.CreateVM_Task) soap.HasFault { 472 return &methods.CreateVM_TaskBody{ 473 Res: &types.CreateVM_TaskResponse{ 474 Returnval: NewTask(&createVM{f, ctx, c, false}).Run(ctx), 475 }, 476 } 477 } 478 479 type registerVM struct { 480 *Folder 481 482 ctx *Context 483 req *types.RegisterVM_Task 484 } 485 486 func (c *registerVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) { 487 host := c.req.Host 488 pool := c.req.Pool 489 490 if c.req.AsTemplate { 491 if host == nil { 492 return nil, &types.InvalidArgument{InvalidProperty: "host"} 493 } else if pool != nil { 494 return nil, &types.InvalidArgument{InvalidProperty: "pool"} 495 } 496 497 pool = hostParent(&c.ctx.Map.Get(*host).(*HostSystem).HostSystem).ResourcePool 498 } else { 499 if pool == nil { 500 return nil, &types.InvalidArgument{InvalidProperty: "pool"} 501 } 502 } 503 504 if c.req.Path == "" { 505 return nil, &types.InvalidArgument{InvalidProperty: "path"} 506 } 507 508 s := c.ctx.Map.SearchIndex() 509 r := s.FindByDatastorePath(&types.FindByDatastorePath{ 510 This: s.Reference(), 511 Path: c.req.Path, 512 Datacenter: c.ctx.Map.getEntityDatacenter(c.Folder).Reference(), 513 }) 514 515 if ref := r.(*methods.FindByDatastorePathBody).Res.Returnval; ref != nil { 516 return nil, &types.AlreadyExists{Name: ref.Value} 517 } 518 519 if c.req.Name == "" { 520 p, err := parseDatastorePath(c.req.Path) 521 if err != nil { 522 return nil, err 523 } 524 525 c.req.Name = path.Dir(p.Path) 526 } 527 528 create := NewTask(&createVM{ 529 Folder: c.Folder, 530 register: true, 531 ctx: c.ctx, 532 req: &types.CreateVM_Task{ 533 This: c.Folder.Reference(), 534 Config: types.VirtualMachineConfigSpec{ 535 Name: c.req.Name, 536 Files: &types.VirtualMachineFileInfo{ 537 VmPathName: c.req.Path, 538 }, 539 }, 540 Pool: *pool, 541 Host: host, 542 }, 543 }) 544 545 create.RunBlocking(c.ctx) 546 547 if create.Info.Error != nil { 548 return nil, create.Info.Error.Fault 549 } 550 551 return create.Info.Result, nil 552 } 553 554 func (f *Folder) RegisterVMTask(ctx *Context, c *types.RegisterVM_Task) soap.HasFault { 555 return &methods.RegisterVM_TaskBody{ 556 Res: &types.RegisterVM_TaskResponse{ 557 Returnval: NewTask(®isterVM{f, ctx, c}).Run(ctx), 558 }, 559 } 560 } 561 562 func (f *Folder) MoveIntoFolderTask(ctx *Context, c *types.MoveIntoFolder_Task) soap.HasFault { 563 task := CreateTask(f, "moveIntoFolder", func(t *Task) (types.AnyType, types.BaseMethodFault) { 564 for _, ref := range c.List { 565 obj := ctx.Map.Get(ref).(mo.Entity) 566 567 parent, ok := ctx.Map.Get(*(obj.Entity()).Parent).(*Folder) 568 569 if !ok || !folderHasChildType(&f.Folder, ref.Type) { 570 return nil, &types.NotSupported{} 571 } 572 573 folderRemoveChild(ctx, &parent.Folder, ref) 574 folderPutChild(ctx, &f.Folder, obj) 575 } 576 577 return nil, nil 578 }) 579 580 return &methods.MoveIntoFolder_TaskBody{ 581 Res: &types.MoveIntoFolder_TaskResponse{ 582 Returnval: task.Run(ctx), 583 }, 584 } 585 } 586 587 func (f *Folder) CreateDVSTask(ctx *Context, req *types.CreateDVS_Task) soap.HasFault { 588 task := CreateTask(f, "createDVS", func(t *Task) (types.AnyType, types.BaseMethodFault) { 589 spec := req.Spec.ConfigSpec.GetDVSConfigSpec() 590 dvs := &DistributedVirtualSwitch{} 591 dvs.Name = spec.Name 592 dvs.Entity().Name = dvs.Name 593 594 if ctx.Map.FindByName(dvs.Name, f.ChildEntity) != nil { 595 return nil, &types.InvalidArgument{InvalidProperty: "name"} 596 } 597 598 dvs.Uuid = newUUID(dvs.Name) 599 600 folderPutChild(ctx, &f.Folder, dvs) 601 602 dvs.Summary = types.DVSSummary{ 603 Name: dvs.Name, 604 Uuid: dvs.Uuid, 605 NumPorts: spec.NumStandalonePorts, 606 ProductInfo: req.Spec.ProductInfo, 607 Description: spec.Description, 608 } 609 610 configInfo := &types.VMwareDVSConfigInfo{ 611 DVSConfigInfo: types.DVSConfigInfo{ 612 Uuid: dvs.Uuid, 613 Name: spec.Name, 614 ConfigVersion: spec.ConfigVersion, 615 NumStandalonePorts: spec.NumStandalonePorts, 616 MaxPorts: spec.MaxPorts, 617 UplinkPortPolicy: spec.UplinkPortPolicy, 618 UplinkPortgroup: spec.UplinkPortgroup, 619 DefaultPortConfig: spec.DefaultPortConfig, 620 ExtensionKey: spec.ExtensionKey, 621 Description: spec.Description, 622 Policy: spec.Policy, 623 VendorSpecificConfig: spec.VendorSpecificConfig, 624 SwitchIpAddress: spec.SwitchIpAddress, 625 DefaultProxySwitchMaxNumPorts: spec.DefaultProxySwitchMaxNumPorts, 626 InfrastructureTrafficResourceConfig: spec.InfrastructureTrafficResourceConfig, 627 NetworkResourceControlVersion: spec.NetworkResourceControlVersion, 628 }, 629 } 630 631 if spec, ok := req.Spec.ConfigSpec.(*types.VMwareDVSConfigSpec); ok { 632 configInfo.LinkDiscoveryProtocolConfig = spec.LinkDiscoveryProtocolConfig 633 configInfo.MaxMtu = spec.MaxMtu 634 configInfo.IpfixConfig = spec.IpfixConfig 635 configInfo.LacpApiVersion = spec.LacpApiVersion 636 configInfo.MulticastFilteringMode = spec.MulticastFilteringMode 637 configInfo.NetworkOffloadSpecId = spec.NetworkOffloadSpecId 638 } 639 640 if spec.Contact != nil { 641 configInfo.Contact = *spec.Contact 642 } 643 644 dvs.Config = configInfo 645 646 if dvs.Summary.ProductInfo == nil { 647 product := ctx.Map.content().About 648 dvs.Summary.ProductInfo = &types.DistributedVirtualSwitchProductSpec{ 649 Name: "DVS", 650 Vendor: product.Vendor, 651 Version: product.Version, 652 Build: product.Build, 653 ForwardingClass: "etherswitch", 654 } 655 } 656 657 ctx.postEvent(&types.DvsCreatedEvent{ 658 DvsEvent: dvs.event(), 659 Parent: folderEventArgument(&f.Folder), 660 }) 661 662 dvs.AddDVPortgroupTask(ctx, &types.AddDVPortgroup_Task{ 663 Spec: []types.DVPortgroupConfigSpec{{ 664 Name: dvs.Name + "-DVUplinks" + strings.TrimPrefix(dvs.Self.Value, "dvs"), 665 Type: string(types.DistributedVirtualPortgroupPortgroupTypeEarlyBinding), 666 NumPorts: 1, 667 DefaultPortConfig: &types.VMwareDVSPortSetting{ 668 Vlan: &types.VmwareDistributedVirtualSwitchTrunkVlanSpec{ 669 VlanId: []types.NumericRange{{Start: 0, End: 4094}}, 670 }, 671 UplinkTeamingPolicy: &types.VmwareUplinkPortTeamingPolicy{ 672 Policy: &types.StringPolicy{ 673 Value: "loadbalance_srcid", 674 }, 675 ReversePolicy: &types.BoolPolicy{ 676 Value: types.NewBool(true), 677 }, 678 NotifySwitches: &types.BoolPolicy{ 679 Value: types.NewBool(true), 680 }, 681 RollingOrder: &types.BoolPolicy{ 682 Value: types.NewBool(true), 683 }, 684 }, 685 }, 686 }}, 687 }) 688 689 return dvs.Reference(), nil 690 }) 691 692 return &methods.CreateDVS_TaskBody{ 693 Res: &types.CreateDVS_TaskResponse{ 694 Returnval: task.Run(ctx), 695 }, 696 } 697 } 698 699 func (f *Folder) RenameTask(ctx *Context, r *types.Rename_Task) soap.HasFault { 700 return RenameTask(ctx, f, r) 701 } 702 703 func (f *Folder) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault { 704 type destroyer interface { 705 mo.Reference 706 DestroyTask(*types.Destroy_Task) soap.HasFault 707 } 708 709 task := CreateTask(f, "destroy", func(*Task) (types.AnyType, types.BaseMethodFault) { 710 // Attempt to destroy all children 711 for _, c := range f.ChildEntity { 712 obj, ok := ctx.Map.Get(c).(destroyer) 713 if !ok { 714 continue 715 } 716 717 var fault types.BaseMethodFault 718 ctx.WithLock(obj, func() { 719 id := obj.DestroyTask(&types.Destroy_Task{ 720 This: c, 721 }).(*methods.Destroy_TaskBody).Res.Returnval 722 723 t := ctx.Map.Get(id).(*Task) 724 t.Wait() 725 if t.Info.Error != nil { 726 fault = t.Info.Error.Fault // For example, can't destroy a powered on VM 727 } 728 }) 729 if fault != nil { 730 return nil, fault 731 } 732 } 733 734 // Remove the folder itself 735 folderRemoveChild(ctx, &ctx.Map.Get(*f.Parent).(*Folder).Folder, f.Self) 736 return nil, nil 737 }) 738 739 return &methods.Destroy_TaskBody{ 740 Res: &types.Destroy_TaskResponse{ 741 Returnval: task.Run(ctx), 742 }, 743 } 744 } 745 746 func (f *Folder) PlaceVmsXCluster(ctx *Context, req *types.PlaceVmsXCluster) soap.HasFault { 747 body := new(methods.PlaceVmsXClusterBody) 748 749 // Reject the request if it is against any folder other than the root folder. 750 if req.This != ctx.Map.content().RootFolder { 751 body.Fault_ = Fault("", new(types.InvalidRequest)) 752 return body 753 } 754 755 pools := req.PlacementSpec.ResourcePools 756 specs := req.PlacementSpec.VmPlacementSpecs 757 758 if len(pools) == 0 { 759 body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "resourcePools"}) 760 return body 761 } 762 763 // Do not allow duplicate clusters. 764 clusters := map[mo.Reference]struct{}{} 765 for _, obj := range pools { 766 o := ctx.Map.Get(obj) 767 pool, ok := o.(*ResourcePool) 768 if !ok { 769 body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "resourcePool"}) 770 return body 771 } 772 if _, exists := clusters[pool.Owner]; exists { 773 body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "clusters"}) 774 return body 775 } 776 clusters[pool.Owner] = struct{}{} 777 } 778 779 // MVP: Only a single VM is supported. 780 if len(specs) != 1 { 781 body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "vmPlacementSpecs"}) 782 return body 783 } 784 785 for _, spec := range specs { 786 if spec.ConfigSpec.Name == "" { 787 body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "configSpec.name"}) 788 return body 789 } 790 } 791 792 body.Res = new(types.PlaceVmsXClusterResponse) 793 hostRequired := req.PlacementSpec.HostRecommRequired != nil && *req.PlacementSpec.HostRecommRequired 794 datastoreRequired := req.PlacementSpec.DatastoreRecommRequired != nil && *req.PlacementSpec.DatastoreRecommRequired 795 796 for _, spec := range specs { 797 pool := ctx.Map.Get(pools[rand.Intn(len(pools))]).(*ResourcePool) 798 cluster := ctx.Map.Get(pool.Owner).(*ClusterComputeResource) 799 800 if len(cluster.Host) == 0 { 801 faults := types.PlaceVmsXClusterResultPlacementFaults{ 802 VmName: spec.ConfigSpec.Name, 803 ResourcePool: pool.Self, 804 Faults: []types.LocalizedMethodFault{ 805 { 806 Fault: &types.GenericDrsFault{}, 807 }, 808 }, 809 } 810 body.Res.Returnval.Faults = append(body.Res.Returnval.Faults, faults) 811 } else { 812 var configSpec *types.VirtualMachineConfigSpec 813 814 res := types.ClusterRecommendation{ 815 Key: "1", 816 Type: "V1", 817 Time: time.Now(), 818 Rating: 1, 819 Reason: string(types.RecommendationReasonCodeXClusterPlacement), 820 ReasonText: string(types.RecommendationReasonCodeXClusterPlacement), 821 Target: &cluster.Self, 822 } 823 824 placementAction := types.ClusterClusterInitialPlacementAction{ 825 Pool: pool.Self, 826 } 827 828 if hostRequired { 829 randomHost := cluster.Host[rand.Intn(len(cluster.Host))] 830 placementAction.TargetHost = &randomHost 831 } 832 833 if datastoreRequired { 834 configSpec = &spec.ConfigSpec 835 836 // TODO: This is just an initial implementation aimed at returning some data but it is not 837 // necessarily fully consistent, like we should ensure the host, if also required, has the 838 // datastore mounted. 839 ds := ctx.Map.Get(cluster.Datastore[rand.Intn(len(cluster.Datastore))]).(*Datastore) 840 841 if configSpec.Files == nil { 842 configSpec.Files = new(types.VirtualMachineFileInfo) 843 } 844 configSpec.Files.VmPathName = fmt.Sprintf("[%[1]s] %[2]s/%[2]s.vmx", ds.Name, spec.ConfigSpec.Name) 845 846 for _, change := range configSpec.DeviceChange { 847 dspec := change.GetVirtualDeviceConfigSpec() 848 849 if dspec.FileOperation != types.VirtualDeviceConfigSpecFileOperationCreate { 850 continue 851 } 852 853 switch dspec.Operation { 854 case types.VirtualDeviceConfigSpecOperationAdd: 855 device := dspec.Device 856 d := device.GetVirtualDevice() 857 858 switch device.(type) { 859 case *types.VirtualDisk: 860 switch b := d.Backing.(type) { 861 case types.BaseVirtualDeviceFileBackingInfo: 862 info := b.GetVirtualDeviceFileBackingInfo() 863 info.Datastore = types.NewReference(ds.Reference()) 864 865 var dsPath object.DatastorePath 866 if dsPath.FromString(info.FileName) { 867 dsPath.Datastore = ds.Name 868 info.FileName = dsPath.String() 869 } 870 } 871 } 872 } 873 } 874 875 placementAction.ConfigSpec = configSpec 876 } 877 878 res.Action = append(res.Action, &placementAction) 879 880 body.Res.Returnval.PlacementInfos = append(body.Res.Returnval.PlacementInfos, 881 types.PlaceVmsXClusterResultPlacementInfo{ 882 VmName: spec.ConfigSpec.Name, 883 Recommendation: res, 884 }, 885 ) 886 } 887 } 888 889 return body 890 }