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  }