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