github.com/vmware/govmomi@v0.51.0/simulator/resource_pool.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 "fmt" 9 "path" 10 "strings" 11 12 "github.com/vmware/govmomi/object" 13 "github.com/vmware/govmomi/simulator/esx" 14 "github.com/vmware/govmomi/vim25/methods" 15 "github.com/vmware/govmomi/vim25/mo" 16 "github.com/vmware/govmomi/vim25/soap" 17 "github.com/vmware/govmomi/vim25/types" 18 ) 19 20 type ResourcePool struct { 21 mo.ResourcePool 22 } 23 24 func asResourcePoolMO(obj mo.Reference) (*mo.ResourcePool, bool) { 25 rp, ok := getManagedObject(obj).Addr().Interface().(*mo.ResourcePool) 26 return rp, ok 27 } 28 29 func resourcePoolHosts(ctx *Context, pool *ResourcePool) []types.ManagedObjectReference { 30 switch owner := ctx.Map.Get(pool.Owner).(type) { 31 case *ClusterComputeResource: 32 return owner.Host 33 case *mo.ComputeResource: 34 return owner.Host 35 default: 36 return nil 37 } 38 } 39 40 func NewResourcePool(ctx *Context) *ResourcePool { 41 pool := &ResourcePool{ 42 ResourcePool: esx.ResourcePool, 43 } 44 45 if ctx.Map.IsVPX() { 46 pool.DisabledMethod = nil // Enable VApp methods for VC 47 } 48 49 return pool 50 } 51 52 func allResourceFieldsSet(info *types.ResourceAllocationInfo) bool { 53 return info.Reservation != nil && 54 info.Limit != nil && 55 info.ExpandableReservation != nil && 56 info.Shares != nil 57 } 58 59 func allResourceFieldsValid(info *types.ResourceAllocationInfo) bool { 60 if info.Reservation != nil { 61 if *info.Reservation < 0 { 62 return false 63 } 64 } 65 66 if info.Limit != nil { 67 if *info.Limit < -1 { 68 return false 69 } 70 } 71 72 if info.Shares != nil { 73 if info.Shares.Level == types.SharesLevelCustom { 74 if info.Shares.Shares < 0 { 75 return false 76 } 77 } 78 } 79 80 if info.OverheadLimit != nil { 81 return false 82 } 83 84 return true 85 } 86 87 func (p *ResourcePool) createChild(ctx *Context, name string, spec types.ResourceConfigSpec) (*ResourcePool, *soap.Fault) { 88 if e := ctx.Map.FindByName(name, p.ResourcePool.ResourcePool); e != nil { 89 return nil, Fault("", &types.DuplicateName{ 90 Name: e.Entity().Name, 91 Object: e.Reference(), 92 }) 93 } 94 95 if !(allResourceFieldsSet(&spec.CpuAllocation) && allResourceFieldsValid(&spec.CpuAllocation)) { 96 return nil, Fault("", &types.InvalidArgument{ 97 InvalidProperty: "spec.cpuAllocation", 98 }) 99 } 100 101 if !(allResourceFieldsSet(&spec.MemoryAllocation) && allResourceFieldsValid(&spec.MemoryAllocation)) { 102 return nil, Fault("", &types.InvalidArgument{ 103 InvalidProperty: "spec.memoryAllocation", 104 }) 105 } 106 107 child := NewResourcePool(ctx) 108 109 child.Name = name 110 child.Owner = p.Owner 111 child.Summary.GetResourcePoolSummary().Name = name 112 child.Config.CpuAllocation = spec.CpuAllocation 113 child.Config.MemoryAllocation = spec.MemoryAllocation 114 child.Config.Entity = spec.Entity 115 116 return child, nil 117 } 118 119 func (p *ResourcePool) CreateResourcePool(ctx *Context, c *types.CreateResourcePool) soap.HasFault { 120 body := &methods.CreateResourcePoolBody{} 121 122 child, err := p.createChild(ctx, c.Name, c.Spec) 123 if err != nil { 124 body.Fault_ = err 125 return body 126 } 127 128 ctx.Map.PutEntity(p, ctx.Map.NewEntity(child)) 129 130 p.ResourcePool.ResourcePool = append(p.ResourcePool.ResourcePool, child.Reference()) 131 132 body.Res = &types.CreateResourcePoolResponse{ 133 Returnval: child.Reference(), 134 } 135 136 return body 137 } 138 139 func updateResourceAllocation(kind string, src, dst *types.ResourceAllocationInfo) types.BaseMethodFault { 140 if !allResourceFieldsValid(src) { 141 return &types.InvalidArgument{ 142 InvalidProperty: fmt.Sprintf("spec.%sAllocation", kind), 143 } 144 } 145 146 if src.Reservation != nil { 147 dst.Reservation = src.Reservation 148 } 149 150 if src.Limit != nil { 151 dst.Limit = src.Limit 152 } 153 154 if src.Shares != nil { 155 dst.Shares = src.Shares 156 } 157 158 return nil 159 } 160 161 func (p *ResourcePool) UpdateConfig(ctx *Context, c *types.UpdateConfig) soap.HasFault { 162 body := &methods.UpdateConfigBody{} 163 164 if c.Name != "" { 165 if e := ctx.Map.FindByName(c.Name, p.ResourcePool.ResourcePool); e != nil { 166 body.Fault_ = Fault("", &types.DuplicateName{ 167 Name: e.Entity().Name, 168 Object: e.Reference(), 169 }) 170 return body 171 } 172 173 p.Name = c.Name 174 } 175 176 spec := c.Config 177 178 if spec != nil { 179 if err := updateResourceAllocation("memory", &spec.MemoryAllocation, &p.Config.MemoryAllocation); err != nil { 180 body.Fault_ = Fault("", err) 181 return body 182 } 183 184 if err := updateResourceAllocation("cpu", &spec.CpuAllocation, &p.Config.CpuAllocation); err != nil { 185 body.Fault_ = Fault("", err) 186 return body 187 } 188 } 189 190 body.Res = &types.UpdateConfigResponse{} 191 192 return body 193 } 194 195 func (a *VirtualApp) ImportVApp(ctx *Context, req *types.ImportVApp) soap.HasFault { 196 return (&ResourcePool{ResourcePool: a.ResourcePool}).ImportVApp(ctx, req) 197 } 198 199 func (p *ResourcePool) ImportVApp(ctx *Context, req *types.ImportVApp) soap.HasFault { 200 body := new(methods.ImportVAppBody) 201 202 spec, ok := req.Spec.(*types.VirtualMachineImportSpec) 203 if !ok { 204 body.Fault_ = Fault(fmt.Sprintf("%T: type not supported", spec), &types.InvalidArgument{InvalidProperty: "spec"}) 205 return body 206 } 207 208 dc := ctx.Map.getEntityDatacenter(p) 209 folder := ctx.Map.Get(dc.VmFolder).(*Folder) 210 if req.Folder != nil { 211 if p.Self.Type == "VirtualApp" { 212 body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "pool"}) 213 return body 214 } 215 folder = ctx.Map.Get(*req.Folder).(*Folder) 216 } 217 218 lease := newHttpNfcLease(ctx) 219 ref := lease.Reference() 220 221 CreateTask(p, "ImportVAppLRO", func(*Task) (types.AnyType, types.BaseMethodFault) { 222 if vapp, ok := spec.ConfigSpec.VAppConfig.(*types.VAppConfigSpec); ok { 223 for _, p := range vapp.Property { 224 if p.Info == nil || isTrue(p.Info.UserConfigurable) { 225 continue 226 } 227 228 if p.Info.Value == "" || p.Info.Value == p.Info.DefaultValue { 229 continue 230 } 231 232 fault := &types.NotUserConfigurableProperty{ 233 VAppPropertyFault: types.VAppPropertyFault{ 234 Id: p.Info.Id, 235 Category: p.Info.Category, 236 Label: p.Info.Label, 237 Type: p.Info.Type, 238 Value: p.Info.Value, 239 }, 240 } 241 242 lease.error(ctx, &types.LocalizedMethodFault{ 243 LocalizedMessage: fmt.Sprintf("Property %s.%s is not user configurable", p.Info.ClassId, p.Info.Id), 244 Fault: fault, 245 }) 246 247 return nil, fault 248 } 249 } 250 251 res := folder.CreateVMTask(ctx, &types.CreateVM_Task{ 252 This: folder.Self, 253 Config: spec.ConfigSpec, 254 Pool: p.Self, 255 Host: req.Host, 256 }) 257 258 ctask := ctx.Map.Get(res.(*methods.CreateVM_TaskBody).Res.Returnval).(*Task) 259 ctask.Wait() 260 261 if ctask.Info.Error != nil { 262 lease.error(ctx, ctask.Info.Error) 263 return nil, ctask.Info.Error.Fault 264 } 265 266 mref := ctask.Info.Result.(types.ManagedObjectReference) 267 vm := ctx.Map.Get(mref).(*VirtualMachine) 268 device := object.VirtualDeviceList(vm.Config.Hardware.Device) 269 ndevice := make(map[string]int) 270 var urls []types.HttpNfcLeaseDeviceUrl 271 u := leaseURL(ctx) 272 273 for _, d := range device { 274 info, ok := d.GetVirtualDevice().Backing.(types.BaseVirtualDeviceFileBackingInfo) 275 if !ok { 276 continue 277 } 278 var file object.DatastorePath 279 file.FromString(info.GetVirtualDeviceFileBackingInfo().FileName) 280 name := path.Base(file.Path) 281 ds := vm.findDatastore(ctx, file.Datastore) 282 lease.files[name] = ds.resolve(ctx, file.Path) 283 284 _, disk := d.(*types.VirtualDisk) 285 kind := device.Type(d) 286 n := ndevice[kind] 287 ndevice[kind]++ 288 289 u.Path = nfcPrefix + path.Join(ref.Value, name) 290 urls = append(urls, types.HttpNfcLeaseDeviceUrl{ 291 Key: fmt.Sprintf("/%s/%s:%d", vm.Self.Value, kind, n), 292 ImportKey: fmt.Sprintf("/%s/%s:%d", vm.Name, kind, n), 293 Url: u.String(), 294 SslThumbprint: "", 295 Disk: types.NewBool(disk), 296 TargetId: name, 297 DatastoreKey: "", 298 FileSize: 0, 299 }) 300 } 301 302 lease.ready(ctx, mref, urls) 303 304 // TODO: keep this task running until lease timeout or marked completed by the client 305 306 return nil, nil 307 }).Run(ctx) 308 309 body.Res = &types.ImportVAppResponse{ 310 Returnval: ref, 311 } 312 313 return body 314 } 315 316 type VirtualApp struct { 317 mo.VirtualApp 318 } 319 320 func NewVAppConfigSpec() types.VAppConfigSpec { 321 spec := types.VAppConfigSpec{ 322 Annotation: "vcsim", 323 VmConfigSpec: types.VmConfigSpec{ 324 Product: []types.VAppProductSpec{ 325 { 326 Info: &types.VAppProductInfo{ 327 Name: "vcsim", 328 Vendor: "VMware", 329 VendorUrl: "http://www.vmware.com/", 330 Version: "0.1", 331 }, 332 ArrayUpdateSpec: types.ArrayUpdateSpec{ 333 Operation: types.ArrayUpdateOperationAdd, 334 }, 335 }, 336 }, 337 }, 338 } 339 340 return spec 341 } 342 343 func (p *ResourcePool) CreateVApp(ctx *Context, req *types.CreateVApp) soap.HasFault { 344 body := &methods.CreateVAppBody{} 345 346 pool, err := p.createChild(ctx, req.Name, req.ResSpec) 347 if err != nil { 348 body.Fault_ = err 349 return body 350 } 351 352 child := &VirtualApp{} 353 child.ResourcePool = pool.ResourcePool 354 child.Self.Type = "VirtualApp" 355 child.ParentFolder = req.VmFolder 356 357 if child.ParentFolder == nil { 358 folder := ctx.Map.getEntityDatacenter(p).VmFolder 359 child.ParentFolder = &folder 360 } 361 362 child.VAppConfig = &types.VAppConfigInfo{ 363 VmConfigInfo: types.VmConfigInfo{}, 364 Annotation: req.ConfigSpec.Annotation, 365 } 366 367 for _, product := range req.ConfigSpec.Product { 368 child.VAppConfig.Product = append(child.VAppConfig.Product, *product.Info) 369 } 370 371 ctx.Map.PutEntity(p, ctx.Map.NewEntity(child)) 372 373 p.ResourcePool.ResourcePool = append(p.ResourcePool.ResourcePool, child.Reference()) 374 375 body.Res = &types.CreateVAppResponse{ 376 Returnval: child.Reference(), 377 } 378 379 return body 380 } 381 382 func (a *VirtualApp) CreateChildVMTask(ctx *Context, req *types.CreateChildVM_Task) soap.HasFault { 383 body := &methods.CreateChildVM_TaskBody{} 384 385 folder := ctx.Map.Get(*a.ParentFolder).(*Folder) 386 387 res := folder.CreateVMTask(ctx, &types.CreateVM_Task{ 388 This: folder.Self, 389 Config: req.Config, 390 Host: req.Host, 391 Pool: req.This, 392 }) 393 394 body.Res = &types.CreateChildVM_TaskResponse{ 395 Returnval: res.(*methods.CreateVM_TaskBody).Res.Returnval, 396 } 397 398 return body 399 } 400 401 func (a *VirtualApp) CloneVAppTask(ctx *Context, req *types.CloneVApp_Task) soap.HasFault { 402 task := CreateTask(a, "cloneVapp", func(t *Task) (types.AnyType, types.BaseMethodFault) { 403 folder := req.Spec.VmFolder 404 if folder == nil { 405 folder = a.ParentFolder 406 } 407 408 rspec := req.Spec.ResourceSpec 409 if rspec == nil { 410 s := types.DefaultResourceConfigSpec() 411 rspec = &s 412 } 413 414 res := a.CreateVApp(ctx, &types.CreateVApp{ 415 This: a.Self, 416 Name: req.Name, 417 ResSpec: *rspec, 418 ConfigSpec: types.VAppConfigSpec{}, 419 VmFolder: folder, 420 }) 421 422 if res.Fault() != nil { 423 return nil, res.Fault().VimFault().(types.BaseMethodFault) 424 } 425 426 target := res.(*methods.CreateVAppBody).Res.Returnval 427 428 for _, ref := range a.Vm { 429 vm := ctx.Map.Get(ref).(*VirtualMachine) 430 431 res := vm.CloneVMTask(ctx, &types.CloneVM_Task{ 432 This: ref, 433 Folder: *folder, 434 Name: req.Name, 435 Spec: types.VirtualMachineCloneSpec{ 436 Location: types.VirtualMachineRelocateSpec{ 437 Pool: &target, 438 Host: req.Spec.Host, 439 }, 440 }, 441 }) 442 443 ctask := ctx.Map.Get(res.(*methods.CloneVM_TaskBody).Res.Returnval).(*Task) 444 ctask.Wait() 445 if ctask.Info.Error != nil { 446 return nil, ctask.Info.Error.Fault 447 } 448 } 449 450 return target, nil 451 }) 452 453 return &methods.CloneVApp_TaskBody{ 454 Res: &types.CloneVApp_TaskResponse{ 455 Returnval: task.Run(ctx), 456 }, 457 } 458 } 459 460 func (a *VirtualApp) CreateVApp(ctx *Context, req *types.CreateVApp) soap.HasFault { 461 return (&ResourcePool{ResourcePool: a.ResourcePool}).CreateVApp(ctx, req) 462 } 463 464 func (a *VirtualApp) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault { 465 return (&ResourcePool{ResourcePool: a.ResourcePool}).DestroyTask(ctx, req) 466 } 467 468 func (p *ResourcePool) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault { 469 task := CreateTask(p, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) { 470 if strings.HasSuffix(p.Parent.Type, "ComputeResource") { 471 // Can't destroy the root pool 472 return nil, &types.InvalidArgument{} 473 } 474 475 parent, _ := asResourcePoolMO(ctx.Map.Get(*p.Parent)) 476 477 // Remove child reference from rp 478 ctx.WithLock(parent, func() { 479 RemoveReference(&parent.ResourcePool, req.This) 480 481 // The grandchildren become children of the parent (rp) 482 for _, ref := range p.ResourcePool.ResourcePool { 483 child := ctx.Map.Get(ref).(*ResourcePool) 484 ctx.WithLock(child, func() { child.Parent = &parent.Self }) 485 parent.ResourcePool = append(parent.ResourcePool, ref) 486 } 487 }) 488 489 // And VMs move to the parent 490 vms := p.ResourcePool.Vm 491 for _, ref := range vms { 492 vm := ctx.Map.Get(ref).(*VirtualMachine) 493 ctx.WithLock(vm, func() { vm.ResourcePool = &parent.Self }) 494 } 495 496 ctx.WithLock(parent, func() { 497 parent.Vm = append(parent.Vm, vms...) 498 }) 499 500 ctx.Map.Remove(ctx, req.This) 501 502 return nil, nil 503 }) 504 505 return &methods.Destroy_TaskBody{ 506 Res: &types.Destroy_TaskResponse{ 507 Returnval: task.Run(ctx), 508 }, 509 } 510 } 511 512 func (p *ResourcePool) DestroyChildren(ctx *Context, req *types.DestroyChildren) soap.HasFault { 513 walk(p, func(child types.ManagedObjectReference) { 514 if child.Type != "ResourcePool" { 515 return 516 } 517 ctx.Map.Get(child).(*ResourcePool).DestroyTask(ctx, &types.Destroy_Task{This: child}) 518 }) 519 520 return &methods.DestroyChildrenBody{Res: new(types.DestroyChildrenResponse)} 521 }