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(&registerVM{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  }