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  }