Repository: vmware/govmomi
Pull request #1969 (2020-05-05 01:27:52)

simulator: refactor folder children operations
The previous approach had the following issues:
- some private methods were attached to Folder, when they really applied to
  mo.Folder. As a result, they prevented using a custom implementation of a
  simulator folder object
- the putChild/removChild methods didn't lock the folder properly, generating
  data races

To address the former, move from private Folder methods to functions that take
a *mo.Folder as first parameter, and adjust call sites.

To address the latter, we need to add proper locking on the *mo.Folder object.
But unfortunately we need the session locking for these, as these operations
are (also) applied during some simulator.Folder API operations, meaning that the
folder is already locked for these.
As a result, we need to pass a simulator.Context around, and adjust the
corresponding call sites.


Patch:
diff --git a/simulator/cluster_compute_resource.go b/simulator/cluster_compute_resource.go
index 97329764..906261d3 100644
--- a/simulator/cluster_compute_resource.go
+++ b/simulator/cluster_compute_resource.go
@@ -349,7 +349,7 @@ func (c *ClusterComputeResource) PlaceVm(ctx *Context, req *types.PlaceVm) soap.
 	return body
 }
 
-func CreateClusterComputeResource(f *Folder, name string, spec types.ClusterConfigSpecEx) (*ClusterComputeResource, types.BaseMethodFault) {
+func CreateClusterComputeResource(ctx *Context, f *Folder, name string, spec types.ClusterConfigSpecEx) (*ClusterComputeResource, types.BaseMethodFault) {
 	if e := Map.FindByName(name, f.ChildEntity); e != nil {
 		return nil, &types.DuplicateName{
 			Name:   e.Entity().Name,
@@ -375,7 +375,7 @@ func CreateClusterComputeResource(f *Folder, name string, spec types.ClusterConf
 	Map.PutEntity(cluster, Map.NewEntity(pool))
 	cluster.ResourcePool = &pool.Self
 
-	f.putChild(cluster)
+	folderPutChild(ctx, &f.Folder, cluster)
 	pool.Owner = cluster.Self
 
 	return cluster, nil
diff --git a/simulator/datacenter.go b/simulator/datacenter.go
index e5028e9d..10d0f199 100644
--- a/simulator/datacenter.go
+++ b/simulator/datacenter.go
@@ -34,7 +34,7 @@ type Datacenter struct {
 }
 
 // NewDatacenter creates a Datacenter and its child folders.
-func NewDatacenter(f *Folder) *Datacenter {
+func NewDatacenter(ctx *Context, f *mo.Folder) *Datacenter {
 	dc := &Datacenter{
 		isESX: f.Self == esx.RootFolder.Self,
 	}
@@ -43,9 +43,9 @@ func NewDatacenter(f *Folder) *Datacenter {
 		dc.Datacenter = esx.Datacenter
 	}
 
-	f.putChild(dc)
+	folderPutChild(ctx, f, dc)
 
-	dc.createFolders()
+	dc.createFolders(ctx)
 
 	return dc
 }
@@ -58,7 +58,7 @@ func (dc *Datacenter) RenameTask(r *types.Rename_Task) soap.HasFault {
 // Every Datacenter has 4 inventory Folders: Vm, Host, Datastore and Network.
 // The ESX folder child types are limited to 1 type.
 // The VC folders have additional child types, including nested folders.
-func (dc *Datacenter) createFolders() {
+func (dc *Datacenter) createFolders(ctx *Context) {
 	folders := []struct {
 		ref   *types.ManagedObjectReference
 		name  string
@@ -101,7 +101,7 @@ func (dc *Datacenter) createFolders() {
 			network.Self.Value = "" // we want a different moid per-DC
 		}
 
-		net.putChild(network)
+		folderPutChild(ctx, &net.Folder, network)
 	}
 }
 
@@ -110,7 +110,7 @@ func (dc *Datacenter) defaultNetwork() []types.ManagedObjectReference {
 }
 
 // folder returns the Datacenter folder that can contain the given object type
-func (dc *Datacenter) folder(obj mo.Entity) *Folder {
+func (dc *Datacenter) folder(obj mo.Entity) *mo.Folder {
 	folders := []types.ManagedObjectReference{
 		dc.VmFolder,
 		dc.HostFolder,
@@ -121,7 +121,7 @@ func (dc *Datacenter) folder(obj mo.Entity) *Folder {
 	rtype := obj.Reference().Type
 
 	for i := range folders {
-		folder := Map.Get(folders[i]).(*Folder)
+		folder, _ := asFolderMO(Map.Get(folders[i]))
 		for _, kind := range folder.ChildType {
 			if rtype == kind {
 				return folder
@@ -170,7 +170,7 @@ func (dc *Datacenter) PowerOnMultiVMTask(ctx *Context, req *types.PowerOnMultiVM
 	}
 }
 
-func (d *Datacenter) DestroyTask(req *types.Destroy_Task) soap.HasFault {
+func (d *Datacenter) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault {
 	task := CreateTask(d, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		folders := []types.ManagedObjectReference{
 			d.VmFolder,
@@ -178,12 +178,14 @@ func (d *Datacenter) DestroyTask(req *types.Destroy_Task) soap.HasFault {
 		}
 
 		for _, ref := range folders {
-			if len(Map.Get(ref).(*Folder).ChildEntity) != 0 {
+			f, _ := asFolderMO(Map.Get(ref))
+			if len(f.ChildEntity) != 0 {
 				return nil, &types.ResourceInUse{}
 			}
 		}
 
-		Map.Get(*d.Parent).(*Folder).removeChild(d.Self)
+		p, _ := asFolderMO(Map.Get(*d.Parent))
+		folderRemoveChild(ctx, p, d.Self)
 
 		return nil, nil
 	})
diff --git a/simulator/datastore.go b/simulator/datastore.go
index c2832e14..62103d32 100644
--- a/simulator/datastore.go
+++ b/simulator/datastore.go
@@ -76,7 +76,8 @@ func (ds *Datastore) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.Has
 			Map.RemoveReference(parent, &parent.Datastore, ds.Self)
 		}
 
-		Map.Get(*ds.Parent).(*Folder).removeChild(ds.Self)
+		p, _ := asFolderMO(Map.Get(*ds.Parent))
+		folderRemoveChild(ctx, p, ds.Self)
 
 		return nil, nil
 	})
diff --git a/simulator/dvs.go b/simulator/dvs.go
index c6a4e589..bc61b3d9 100644
--- a/simulator/dvs.go
+++ b/simulator/dvs.go
@@ -29,7 +29,7 @@ type DistributedVirtualSwitch struct {
 	mo.DistributedVirtualSwitch
 }
 
-func (s *DistributedVirtualSwitch) AddDVPortgroupTask(c *types.AddDVPortgroup_Task) soap.HasFault {
+func (s *DistributedVirtualSwitch) AddDVPortgroupTask(ctx *Context, c *types.AddDVPortgroup_Task) soap.HasFault {
 	task := CreateTask(s, "addDVPortgroup", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		f := Map.getEntityParent(s, "Folder").(*Folder)
 
@@ -48,7 +48,7 @@ func (s *DistributedVirtualSwitch) AddDVPortgroupTask(c *types.AddDVPortgroup_Ta
 				}
 			}
 
-			f.putChild(pg)
+			folderPutChild(ctx, &f.Folder, pg)
 
 			pg.Key = pg.Self.Value
 			pg.Config = types.DVPortgroupConfigInfo{
@@ -235,10 +235,10 @@ func (s *DistributedVirtualSwitch) FetchDVPorts(req *types.FetchDVPorts) soap.Ha
 	return body
 }
 
-func (s *DistributedVirtualSwitch) DestroyTask(req *types.Destroy_Task) soap.HasFault {
+func (s *DistributedVirtualSwitch) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault {
 	task := CreateTask(s, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		f := Map.getEntityParent(s, "Folder").(*Folder)
-		f.removeChild(s.Reference())
+		folderRemoveChild(ctx, &f.Folder, s.Reference())
 		return nil, nil
 	})
 
diff --git a/simulator/entity.go b/simulator/entity.go
index c865c9ff..a18c7845 100644
--- a/simulator/entity.go
+++ b/simulator/entity.go
@@ -27,7 +27,7 @@ func RenameTask(e mo.Entity, r *types.Rename_Task) soap.HasFault {
 	task := CreateTask(e, "rename", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		obj := Map.Get(r.This).(mo.Entity).Entity()
 
-		if parent, ok := Map.Get(*obj.Parent).(*Folder); ok {
+		if parent, ok := asFolderMO(Map.Get(*obj.Parent)); ok {
 			if Map.FindByName(r.NewName, parent.ChildEntity) != nil {
 				return nil, &types.InvalidArgument{InvalidProperty: "name"}
 			}
diff --git a/simulator/file_manager.go b/simulator/file_manager.go
index e78213f4..831b1b87 100644
--- a/simulator/file_manager.go
+++ b/simulator/file_manager.go
@@ -36,21 +36,27 @@ type FileManager struct {
 func (f *FileManager) findDatastore(ref mo.Reference, name string) (*Datastore, types.BaseMethodFault) {
 	var refs []types.ManagedObjectReference
 
-	switch obj := ref.(type) {
-	case *Folder:
-		refs = obj.ChildEntity
-	case *StoragePod:
-		refs = obj.ChildEntity
+	if d, ok := asFolderMO(ref); ok {
+		refs = d.ChildEntity
+	}
+	if p, ok := ref.(*StoragePod); ok {
+		refs = p.ChildEntity
 	}
 
 	for _, ref := range refs {
-		switch obj := Map.Get(ref).(type) {
-		case *Datastore:
-			if obj.Name == name {
-				return obj, nil
+		obj := Map.Get(ref)
+
+		if ds, ok := obj.(*Datastore); ok && ds.Name == name {
+			return ds, nil
+		}
+		if p, ok := obj.(*StoragePod); ok {
+			ds, _ := f.findDatastore(p, name)
+			if ds != nil {
+				return ds, nil
 			}
-		case *Folder, *StoragePod:
-			ds, _ := f.findDatastore(obj, name)
+		}
+		if d, ok := asFolderMO(obj); ok {
+			ds, _ := f.findDatastore(d, name)
 			if ds != nil {
 				return ds, nil
 			}
diff --git a/simulator/folder.go b/simulator/folder.go
index 7919a5cd..cf26bed9 100644
--- a/simulator/folder.go
+++ b/simulator/folder.go
@@ -35,7 +35,15 @@ type Folder struct {
 	mo.Folder
 }
 
-func (f *Folder) eventArgument() types.FolderEventArgument {
+func asFolderMO(obj mo.Reference) (*mo.Folder, bool) {
+	if obj == nil {
+		return nil, false
+	}
+	f, ok := getManagedObject(obj).Addr().Interface().(*mo.Folder)
+	return f, ok
+}
+
+func folderEventArgument(f *mo.Folder) types.FolderEventArgument {
 	return types.FolderEventArgument{
 		Folder:              f.Self,
 		EntityEventArgument: types.EntityEventArgument{Name: f.Name},
@@ -43,7 +51,7 @@ func (f *Folder) eventArgument() types.FolderEventArgument {
 }
 
 // update references when objects are added/removed from a Folder
-func (f *Folder) update(o mo.Reference, u func(mo.Reference, *[]types.ManagedObjectReference, types.ManagedObjectReference)) {
+func folderUpdate(f *mo.Folder, o mo.Reference, u func(mo.Reference, *[]types.ManagedObjectReference, types.ManagedObjectReference)) {
 	ref := o.Reference()
 
 	if f.Parent == nil {
@@ -76,32 +84,37 @@ func networkSummary(n *mo.Network) types.BaseNetworkSummary {
 	}
 }
 
-func (f *Folder) putChild(o mo.Entity) {
+func folderPutChild(ctx *Context, f *mo.Folder, o mo.Entity) {
 	Map.PutEntity(f, o)
 
-	f.ChildEntity = append(f.ChildEntity, o.Reference())
-
-	f.update(o, Map.AddReference)
-
-	switch e := o.(type) {
-	case *mo.Network:
-		e.Summary = networkSummary(e)
-	case *mo.OpaqueNetwork:
-		e.Summary = networkSummary(&e.Network)
-	case *DistributedVirtualPortgroup:
-		e.Summary = networkSummary(&e.Network)
-	}
+	ctx.WithLock(f, func() {
+		f.ChildEntity = append(f.ChildEntity, o.Reference())
+		folderUpdate(f, o, Map.AddReference)
+
+		ctx.WithLock(o, func() {
+			switch e := o.(type) {
+			case *mo.Network:
+				e.Summary = networkSummary(e)
+			case *mo.OpaqueNetwork:
+				e.Summary = networkSummary(&e.Network)
+			case *DistributedVirtualPortgroup:
+				e.Summary = networkSummary(&e.Network)
+			}
+		})
+	})
 }
 
-func (f *Folder) removeChild(o mo.Reference) {
+func folderRemoveChild(ctx *Context, f *mo.Folder, o mo.Reference) {
 	Map.Remove(o.Reference())
 
-	RemoveReference(&f.ChildEntity, o.Reference())
+	ctx.WithLock(f, func() {
+		RemoveReference(&f.ChildEntity, o.Reference())
 
-	f.update(o, Map.RemoveReference)
+		folderUpdate(f, o, Map.RemoveReference)
+	})
 }
 
-func (f *Folder) hasChildType(kind string) bool {
+func folderHasChildType(f *mo.Folder, kind string) bool {
 	for _, t := range f.ChildType {
 		if t == kind {
 			return true
@@ -117,7 +130,7 @@ func (f *Folder) typeNotSupported() *soap.Fault {
 // AddOpaqueNetwork adds an OpaqueNetwork type to the inventory, with default backing to that of an nsx.LogicalSwitch.
 // The vSphere API does not have a method to add this directly, so it must either be called directly or via Model.OpaqueNetwork setting.
 func (f *Folder) AddOpaqueNetwork(summary types.OpaqueNetworkSummary) error {
-	if !f.hasChildType("Network") {
+	if !folderHasChildType(&f.Folder, "Network") {
 		return errors.New("not a network folder")
 	}
 
@@ -141,19 +154,19 @@ func (f *Folder) AddOpaqueNetwork(summary types.OpaqueNetworkSummary) error {
 	net.Network.Name = summary.Name
 	net.Summary = &summary
 
-	f.putChild(net)
+	folderPutChild(internalContext, &f.Folder, net)
 
 	return nil
 }
 
 type addStandaloneHost struct {
 	*Folder
-
+	ctx *Context
 	req *types.AddStandaloneHost_Task
 }
 
 func (add *addStandaloneHost) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
-	host, err := CreateStandaloneHost(add.Folder, add.req.Spec)
+	host, err := CreateStandaloneHost(add.ctx, add.Folder, add.req.Spec)
 	if err != nil {
 		return nil, err
 	}
@@ -165,12 +178,12 @@ func (add *addStandaloneHost) Run(task *Task) (types.AnyType, types.BaseMethodFa
 	return host.Reference(), nil
 }
 
-func (f *Folder) AddStandaloneHostTask(a *types.AddStandaloneHost_Task) soap.HasFault {
+func (f *Folder) AddStandaloneHostTask(ctx *Context, a *types.AddStandaloneHost_Task) soap.HasFault {
 	r := &methods.AddStandaloneHost_TaskBody{}
 
-	if f.hasChildType("ComputeResource") && f.hasChildType("Folder") {
+	if folderHasChildType(&f.Folder, "ComputeResource") && folderHasChildType(&f.Folder, "Folder") {
 		r.Res = &types.AddStandaloneHost_TaskResponse{
-			Returnval: NewTask(&addStandaloneHost{f, a}).Run(),
+			Returnval: NewTask(&addStandaloneHost{f, ctx, a}).Run(),
 		}
 	} else {
 		r.Fault_ = f.typeNotSupported()
@@ -179,10 +192,10 @@ func (f *Folder) AddStandaloneHostTask(a *types.AddStandaloneHost_Task) soap.Has
 	return r
 }
 
-func (f *Folder) CreateFolder(c *types.CreateFolder) soap.HasFault {
+func (f *Folder) CreateFolder(ctx *Context, c *types.CreateFolder) soap.HasFault {
 	r := &methods.CreateFolderBody{}
 
-	if f.hasChildType("Folder") {
+	if folderHasChildType(&f.Folder, "Folder") {
 		if obj := Map.FindByName(c.Name, f.ChildEntity); obj != nil {
 			r.Fault_ = Fault("", &types.DuplicateName{
 				Name:   c.Name,
@@ -197,7 +210,7 @@ func (f *Folder) CreateFolder(c *types.CreateFolder) soap.HasFault {
 		folder.Name = c.Name
 		folder.ChildType = f.ChildType
 
-		f.putChild(folder)
+		folderPutChild(ctx, &f.Folder, folder)
 
 		r.Res = &types.CreateFolderResponse{
 			Returnval: folder.Self,
@@ -214,10 +227,10 @@ type StoragePod struct {
 	mo.StoragePod
 }
 
-func (f *Folder) CreateStoragePod(c *types.CreateStoragePod) soap.HasFault {
+func (f *Folder) CreateStoragePod(ctx *Context, c *types.CreateStoragePod) soap.HasFault {
 	r := &methods.CreateStoragePodBody{}
 
-	if f.hasChildType("StoragePod") {
+	if folderHasChildType(&f.Folder, "StoragePod") {
 		if obj := Map.FindByName(c.Name, f.ChildEntity); obj != nil {
 			r.Fault_ = Fault("", &types.DuplicateName{
 				Name:   c.Name,
@@ -235,7 +248,7 @@ func (f *Folder) CreateStoragePod(c *types.CreateStoragePod) soap.HasFault {
 		pod.PodStorageDrsEntry = new(types.PodStorageDrsEntry)
 		pod.PodStorageDrsEntry.StorageDrsConfig.PodConfig.Enabled = true
 
-		f.putChild(pod)
+		folderPutChild(ctx, &f.Folder, pod)
 
 		r.Res = &types.CreateStoragePodResponse{
 			Returnval: pod.Self,
@@ -247,9 +260,9 @@ func (f *Folder) CreateStoragePod(c *types.CreateStoragePod) soap.HasFault {
 	return r
 }
 
-func (p *StoragePod) MoveIntoFolderTask(c *types.MoveIntoFolder_Task) soap.HasFault {
+func (p *StoragePod) MoveIntoFolderTask(ctx *Context, c *types.MoveIntoFolder_Task) soap.HasFault {
 	f := &Folder{Folder: p.Folder}
-	res := f.MoveIntoFolderTask(c)
+	res := f.MoveIntoFolderTask(ctx, c)
 	p.ChildEntity = append(p.ChildEntity, f.ChildEntity...)
 	return res
 }
@@ -257,8 +270,8 @@ func (p *StoragePod) MoveIntoFolderTask(c *types.MoveIntoFolder_Task) soap.HasFa
 func (f *Folder) CreateDatacenter(ctx *Context, c *types.CreateDatacenter) soap.HasFault {
 	r := &methods.CreateDatacenterBody{}
 
-	if f.hasChildType("Datacenter") && f.hasChildType("Folder") {
-		dc := NewDatacenter(f)
+	if folderHasChildType(&f.Folder, "Datacenter") && folderHasChildType(&f.Folder, "Folder") {
+		dc := NewDatacenter(ctx, &f.Folder)
 
 		dc.Name = c.Name
 
@@ -272,7 +285,7 @@ func (f *Folder) CreateDatacenter(ctx *Context, c *types.CreateDatacenter) soap.
 					Datacenter: datacenterEventArgument(dc),
 				},
 			},
-			Parent: f.eventArgument(),
+			Parent: folderEventArgument(&f.Folder),
 		})
 	} else {
 		r.Fault_ = f.typeNotSupported()
@@ -281,11 +294,11 @@ func (f *Folder) CreateDatacenter(ctx *Context, c *types.CreateDatacenter) soap.
 	return r
 }
 
-func (f *Folder) CreateClusterEx(c *types.CreateClusterEx) soap.HasFault {
+func (f *Folder) CreateClusterEx(ctx *Context, c *types.CreateClusterEx) soap.HasFault {
 	r := &methods.CreateClusterExBody{}
 
-	if f.hasChildType("ComputeResource") && f.hasChildType("Folder") {
-		cluster, err := CreateClusterComputeResource(f, c.Name, c.Spec)
+	if folderHasChildType(&f.Folder, "ComputeResource") && folderHasChildType(&f.Folder, "Folder") {
+		cluster, err := CreateClusterComputeResource(ctx, f, c.Name, c.Spec)
 		if err != nil {
 			r.Fault_ = Fault("", err)
 			return r
@@ -327,9 +340,9 @@ func hostsWithDatastore(hosts []types.ManagedObjectReference, path string) []typ
 }
 
 func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
-	vm, err := NewVirtualMachine(c.Folder.Self, &c.req.Config)
+	vm, err := NewVirtualMachine(c.ctx, c.Folder.Self, &c.req.Config)
 	if err != nil {
-		c.Folder.removeChild(vm)
+		folderRemoveChild(c.ctx, &c.Folder.Folder, vm)
 		return nil, err
 	}
 
@@ -370,7 +383,7 @@ func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 
 	err = vm.create(&c.req.Config, c.register)
 	if err != nil {
-		c.Folder.removeChild(vm)
+		folderRemoveChild(c.ctx, &c.Folder.Folder, vm)
 		return nil, err
 	}
 
@@ -509,19 +522,19 @@ func (f *Folder) RegisterVMTask(ctx *Context, c *types.RegisterVM_Task) soap.Has
 	}
 }
 
-func (f *Folder) MoveIntoFolderTask(c *types.MoveIntoFolder_Task) soap.HasFault {
+func (f *Folder) MoveIntoFolderTask(ctx *Context, c *types.MoveIntoFolder_Task) soap.HasFault {
 	task := CreateTask(f, "moveIntoFolder", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		for _, ref := range c.List {
 			obj := Map.Get(ref).(mo.Entity)
 
 			parent, ok := Map.Get(*(obj.Entity()).Parent).(*Folder)
 
-			if !ok || !f.hasChildType(ref.Type) {
+			if !ok || !folderHasChildType(&f.Folder, ref.Type) {
 				return nil, &types.NotSupported{}
 			}
 
-			parent.removeChild(ref)
-			f.putChild(obj)
+			folderRemoveChild(ctx, &parent.Folder, ref)
+			folderPutChild(ctx, &f.Folder, obj)
 		}
 
 		return nil, nil
@@ -534,7 +547,7 @@ func (f *Folder) MoveIntoFolderTask(c *types.MoveIntoFolder_Task) soap.HasFault
 	}
 }
 
-func (f *Folder) CreateDVSTask(req *types.CreateDVS_Task) soap.HasFault {
+func (f *Folder) CreateDVSTask(ctx *Context, req *types.CreateDVS_Task) soap.HasFault {
 	task := CreateTask(f, "createDVS", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		spec := req.Spec.ConfigSpec.GetDVSConfigSpec()
 		dvs := &DistributedVirtualSwitch{}
@@ -547,7 +560,7 @@ func (f *Folder) CreateDVSTask(req *types.CreateDVS_Task) soap.HasFault {
 
 		dvs.Uuid = newUUID(dvs.Name)
 
-		f.putChild(dvs)
+		folderPutChild(ctx, &f.Folder, dvs)
 
 		dvs.Summary = types.DVSSummary{
 			Name:        dvs.Name,
@@ -595,7 +608,7 @@ func (f *Folder) CreateDVSTask(req *types.CreateDVS_Task) soap.HasFault {
 			}
 		}
 
-		dvs.AddDVPortgroupTask(&types.AddDVPortgroup_Task{
+		dvs.AddDVPortgroupTask(ctx, &types.AddDVPortgroup_Task{
 			Spec: []types.DVPortgroupConfigSpec{{
 				Name: dvs.Name + "-DVUplinks" + strings.TrimPrefix(dvs.Self.Value, "dvs"),
 				DefaultPortConfig: &types.VMwareDVSPortSetting{
@@ -634,7 +647,7 @@ func (f *Folder) RenameTask(r *types.Rename_Task) soap.HasFault {
 	return RenameTask(f, r)
 }
 
-func (f *Folder) DestroyTask(req *types.Destroy_Task) soap.HasFault {
+func (f *Folder) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault {
 	type destroyer interface {
 		mo.Reference
 		DestroyTask(*types.Destroy_Task) soap.HasFault
@@ -665,7 +678,7 @@ func (f *Folder) DestroyTask(req *types.Destroy_Task) soap.HasFault {
 		}
 
 		// Remove the folder itself
-		Map.Get(*f.Parent).(*Folder).removeChild(f.Self)
+		folderRemoveChild(ctx, &Map.Get(*f.Parent).(*Folder).Folder, f.Self)
 		return nil, nil
 	})
 
diff --git a/simulator/host_datastore_system.go b/simulator/host_datastore_system.go
index 38084649..a4052879 100644
--- a/simulator/host_datastore_system.go
+++ b/simulator/host_datastore_system.go
@@ -32,7 +32,7 @@ type HostDatastoreSystem struct {
 	Host *mo.HostSystem
 }
 
-func (dss *HostDatastoreSystem) add(ds *Datastore) *soap.Fault {
+func (dss *HostDatastoreSystem) add(ctx *Context, ds *Datastore) *soap.Fault {
 	info := ds.Info.GetDatastoreInfo()
 
 	info.Name = ds.Name
@@ -87,12 +87,12 @@ func (dss *HostDatastoreSystem) add(ds *Datastore) *soap.Fault {
 	browser.Datastore = dss.Datastore
 	ds.Browser = Map.Put(browser).Reference()
 
-	folder.putChild(ds)
+	folderPutChild(ctx, folder, ds)
 
 	return nil
 }
 
-func (dss *HostDatastoreSystem) CreateLocalDatastore(c *types.CreateLocalDatastore) soap.HasFault {
+func (dss *HostDatastoreSystem) CreateLocalDatastore(ctx *Context, c *types.CreateLocalDatastore) soap.HasFault {
 	r := &methods.CreateLocalDatastoreBody{}
 
 	ds := &Datastore{}
@@ -111,7 +111,7 @@ func (dss *HostDatastoreSystem) CreateLocalDatastore(c *types.CreateLocalDatasto
 	ds.Summary.MaintenanceMode = string(types.DatastoreSummaryMaintenanceModeStateNormal)
 	ds.Summary.Accessible = true
 
-	if err := dss.add(ds); err != nil {
+	if err := dss.add(ctx, ds); err != nil {
 		r.Fault_ = err
 		return r
 	}
@@ -134,7 +134,7 @@ func (dss *HostDatastoreSystem) CreateLocalDatastore(c *types.CreateLocalDatasto
 	return r
 }
 
-func (dss *HostDatastoreSystem) CreateNasDatastore(c *types.CreateNasDatastore) soap.HasFault {
+func (dss *HostDatastoreSystem) CreateNasDatastore(ctx *Context, c *types.CreateNasDatastore) soap.HasFault {
 	r := &methods.CreateNasDatastoreBody{}
 
 	ds := &Datastore{}
@@ -159,7 +159,7 @@ func (dss *HostDatastoreSystem) CreateNasDatastore(c *types.CreateNasDatastore)
 	ds.Summary.MaintenanceMode = string(types.DatastoreSummaryMaintenanceModeStateNormal)
 	ds.Summary.Accessible = true
 
-	if err := dss.add(ds); err != nil {
+	if err := dss.add(ctx, ds); err != nil {
 		r.Fault_ = err
 		return r
 	}
diff --git a/simulator/host_network_system.go b/simulator/host_network_system.go
index a5f1ad8a..6dbe7ea1 100644
--- a/simulator/host_network_system.go
+++ b/simulator/host_network_system.go
@@ -88,7 +88,7 @@ func (s *HostNetworkSystem) RemoveVirtualSwitch(c *types.RemoveVirtualSwitch) so
 	return r
 }
 
-func (s *HostNetworkSystem) AddPortGroup(c *types.AddPortGroup) soap.HasFault {
+func (s *HostNetworkSystem) AddPortGroup(ctx *Context, c *types.AddPortGroup) soap.HasFault {
 	var vswitch *types.HostVirtualSwitch
 
 	r := &methods.AddPortGroupBody{}
@@ -125,7 +125,7 @@ func (s *HostNetworkSystem) AddPortGroup(c *types.AddPortGroup) soap.HasFault {
 		return r
 	}
 
-	folder.putChild(network)
+	folderPutChild(ctx, &folder.Folder, network)
 
 	vswitch.Portgroup = append(vswitch.Portgroup, c.Portgrp.Name)
 
@@ -140,7 +140,7 @@ func (s *HostNetworkSystem) AddPortGroup(c *types.AddPortGroup) soap.HasFault {
 	return r
 }
 
-func (s *HostNetworkSystem) RemovePortGroup(c *types.RemovePortGroup) soap.HasFault {
+func (s *HostNetworkSystem) RemovePortGroup(ctx *Context, c *types.RemovePortGroup) soap.HasFault {
 	var vswitch *types.HostVirtualSwitch
 
 	r := &methods.RemovePortGroupBody{}
@@ -161,7 +161,7 @@ func (s *HostNetworkSystem) RemovePortGroup(c *types.RemovePortGroup) soap.HasFa
 
 	folder := s.folder()
 	e := Map.FindByName(c.PgName, folder.ChildEntity)
-	folder.removeChild(e.Reference())
+	folderRemoveChild(ctx, &folder.Folder, e.Reference())
 
 	for i, pg := range s.NetworkInfo.Portgroup {
 		if pg.Spec.Name == c.PgName {
diff --git a/simulator/host_system.go b/simulator/host_system.go
index 163da9bd..c0b2e986 100644
--- a/simulator/host_system.go
+++ b/simulator/host_system.go
@@ -147,8 +147,8 @@ func addComputeResource(s *types.ComputeResourceSummary, h *HostSystem) {
 
 // CreateDefaultESX creates a standalone ESX
 // Adds objects of type: Datacenter, Network, ComputeResource, ResourcePool and HostSystem
-func CreateDefaultESX(f *Folder) {
-	dc := NewDatacenter(f)
+func CreateDefaultESX(ctx *Context, f *Folder) {
+	dc := NewDatacenter(ctx, &f.Folder)
 
 	host := NewHostSystem(esx.HostSystem)
 
@@ -171,12 +171,12 @@ func CreateDefaultESX(f *Folder) {
 	Map.PutEntity(cr, pool)
 	pool.Owner = cr.Self
 
-	Map.Get(dc.HostFolder).(*Folder).putChild(cr)
+	folderPutChild(ctx, &Map.Get(dc.HostFolder).(*Folder).Folder, cr)
 }
 
 // CreateStandaloneHost uses esx.HostSystem as a template, applying the given spec
 // and creating the ComputeResource parent and ResourcePool sibling.
-func CreateStandaloneHost(f *Folder, spec types.HostConnectSpec) (*HostSystem, types.BaseMethodFault) {
+func CreateStandaloneHost(ctx *Context, f *Folder, spec types.HostConnectSpec) (*HostSystem, types.BaseMethodFault) {
 	if spec.HostName == "" {
 		return nil, &types.NoHost{}
 	}
@@ -206,7 +206,7 @@ func CreateStandaloneHost(f *Folder, spec types.HostConnectSpec) (*HostSystem, t
 	cr.Host = append(cr.Host, host.Reference())
 	cr.ResourcePool = &pool.Self
 
-	f.putChild(cr)
+	folderPutChild(ctx, &f.Folder, cr)
 	pool.Owner = cr.Self
 	host.Network = cr.Network
 
@@ -222,7 +222,7 @@ func (h *HostSystem) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.Has
 		ctx.postEvent(&types.HostRemovedEvent{HostEvent: h.event()})
 
 		f := Map.getEntityParent(h, "Folder").(*Folder)
-		f.removeChild(h.Reference())
+		folderRemoveChild(ctx, &f.Folder, h.Reference())
 
 		return nil, nil
 	})
diff --git a/simulator/model.go b/simulator/model.go
index 594df672..f014f3c2 100644
--- a/simulator/model.go
+++ b/simulator/model.go
@@ -299,7 +299,7 @@ func loadObject(content types.ObjectContent) (mo.Reference, error) {
 // resolveReferences attempts to resolve any object references that were not included via Load()
 // example: Load's dir only contains a single OpaqueNetwork, we need to create a Datacenter and
 // place the OpaqueNetwork in the Datacenter's network folder.
-func (m *Model) resolveReferences() error {
+func (m *Model) resolveReferences(ctx *Context) error {
 	dc, ok := Map.Any("Datacenter").(*Datacenter)
 	if !ok {
 		// Need to have at least 1 Datacenter
@@ -327,7 +327,7 @@ func (m *Model) resolveReferences() error {
 				folder := dc.folder(me)
 				e.Parent = &folder.Self
 				log.Printf("%s adopted %s", e.Parent, ref)
-				folder.putChild(me)
+				folderPutChild(ctx, folder, me)
 			default:
 				return fmt.Errorf("unable to foster %s with parent type=%s", ref, e.Parent.Type)
 			}
@@ -340,6 +340,7 @@ func (m *Model) resolveReferences() error {
 
 // Load Model from the given directory, as created by the 'govc object.save' command.
 func (m *Model) Load(dir string) error {
+	ctx := internalContext
 	var s *ServiceInstance
 
 	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
@@ -395,14 +396,14 @@ func (m *Model) Load(dir string) error {
 
 	m.Service = New(s)
 
-	return m.resolveReferences()
+	return m.resolveReferences(ctx)
 }
 
 // Create populates the Model with the given ModelConfig
 func (m *Model) Create() error {
+	ctx := internalContext
 	m.Service = New(NewServiceInstance(m.ServiceContent, m.RootFolder))
 
-	ctx := context.Background()
 	client := m.Service.client
 	root := object.NewRootFolder(client)
 
diff --git a/simulator/portgroup.go b/simulator/portgroup.go
index 7abe1970..17832742 100644
--- a/simulator/portgroup.go
+++ b/simulator/portgroup.go
@@ -52,14 +52,14 @@ func (s *DistributedVirtualPortgroup) ReconfigureDVPortgroupTask(req *types.Reco
 	}
 }
 
-func (s *DistributedVirtualPortgroup) DestroyTask(req *types.Destroy_Task) soap.HasFault {
+func (s *DistributedVirtualPortgroup) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault {
 	task := CreateTask(s, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		vswitch := Map.Get(*s.Config.DistributedVirtualSwitch).(*DistributedVirtualSwitch)
 		Map.RemoveReference(vswitch, &vswitch.Portgroup, s.Reference())
 		Map.removeString(vswitch, &vswitch.Summary.PortgroupName, s.Name)
 
 		f := Map.getEntityParent(vswitch, "Folder").(*Folder)
-		f.removeChild(s.Reference())
+		folderRemoveChild(ctx, &f.Folder, s.Reference())
 
 		return nil, nil
 	})
diff --git a/simulator/registry.go b/simulator/registry.go
index 5b19dba0..22d419e6 100644
--- a/simulator/registry.go
+++ b/simulator/registry.go
@@ -311,7 +311,7 @@ func (r *Registry) getEntityDatacenter(item mo.Entity) *Datacenter {
 	return nil
 }
 
-func (r *Registry) getEntityFolder(item mo.Entity, kind string) *Folder {
+func (r *Registry) getEntityFolder(item mo.Entity, kind string) *mo.Folder {
 	dc := Map.getEntityDatacenter(item)
 
 	var ref types.ManagedObjectReference
@@ -321,12 +321,12 @@ func (r *Registry) getEntityFolder(item mo.Entity, kind string) *Folder {
 		ref = dc.DatastoreFolder
 	}
 
-	folder := r.Get(ref).(*Folder)
+	folder, _ := asFolderMO(r.Get(ref))
 
 	// If Model was created with Folder option, use that Folder; else use top-level folder
 	for _, child := range folder.ChildEntity {
 		if child.Type == "Folder" {
-			folder = Map.Get(child).(*Folder)
+			folder, _ = asFolderMO(Map.Get(child))
 			break
 		}
 	}
diff --git a/simulator/service_instance.go b/simulator/service_instance.go
index 1c51d896..39145642 100644
--- a/simulator/service_instance.go
+++ b/simulator/service_instance.go
@@ -46,7 +46,7 @@ func NewServiceInstance(content types.ServiceContent, folder mo.Folder) *Service
 	Map.Put(f)
 
 	if content.About.ApiType == "HostAgent" {
-		CreateDefaultESX(f)
+		CreateDefaultESX(internalContext, f)
 	} else {
 		content.About.InstanceUuid = uuid.New().String()
 	}
diff --git a/simulator/virtual_machine.go b/simulator/virtual_machine.go
index 151222bd..2089acde 100644
--- a/simulator/virtual_machine.go
+++ b/simulator/virtual_machine.go
@@ -55,11 +55,13 @@ func asVirtualMachineMO(obj mo.Reference) (*mo.VirtualMachine, bool) {
 	return vm, ok
 }
 
-func NewVirtualMachine(parent types.ManagedObjectReference, spec *types.VirtualMachineConfigSpec) (*VirtualMachine, types.BaseMethodFault) {
+func NewVirtualMachine(ctx *Context, parent types.ManagedObjectReference, spec *types.VirtualMachineConfigSpec) (*VirtualMachine, types.BaseMethodFault) {
 	vm := &VirtualMachine{}
 	vm.Parent = &parent
-	folder := Map.Get(parent).(*Folder)
-	folder.putChild(vm)
+
+	folder := Map.Get(parent)
+	f, _ := asFolderMO(folder)
+	folderPutChild(ctx, f, vm)
 
 	if spec.Name == "" {
 		return vm, &types.InvalidVmConfig{Property: "configSpec.name"}
@@ -98,7 +100,7 @@ func NewVirtualMachine(parent types.ManagedObjectReference, spec *types.VirtualM
 		vmx.Path = spec.Name
 	}
 
-	dc := Map.getEntityDatacenter(folder)
+	dc := Map.getEntityDatacenter(folder.(mo.Entity))
 	ds := Map.FindByName(vmx.Datastore, dc.Datastore).(*Datastore)
 	dir := path.Join(ds.Info.GetDatastoreInfo().Url, vmx.Path)
 
@@ -1574,8 +1576,8 @@ func (vm *VirtualMachine) UnregisterVM(ctx *Context, c *types.UnregisterVM) soap
 	}
 
 	ctx.postEvent(&types.VmRemovedEvent{VmEvent: vm.event()})
-	if f, ok := Map.getEntityParent(vm, "Folder").(*Folder); ok {
-		f.removeChild(c.This)
+	if f, ok := asFolderMO(Map.getEntityParent(vm, "Folder")); ok {
+		folderRemoveChild(ctx, f, c.This)
 	}
 
 	r.Res = new(types.UnregisterVMResponse)
@@ -1583,6 +1585,10 @@ func (vm *VirtualMachine) UnregisterVM(ctx *Context, c *types.UnregisterVM) soap
 	return r
 }
 
+type vmFolder interface {
+	CreateVMTask(ctx *Context, c *types.CreateVM_Task) soap.HasFault
+}
+
 func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soap.HasFault {
 	pool := req.Spec.Location.Pool
 	if pool == nil {
@@ -1590,7 +1596,7 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
 			pool = vm.ResourcePool
 		}
 	}
-	folder := Map.Get(req.Folder).(*Folder)
+	folder, _ := asFolderMO(Map.Get(req.Folder))
 	host := Map.Get(*vm.Runtime.Host).(*HostSystem)
 	event := vm.event()
 
@@ -1598,7 +1604,7 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
 		VmCloneEvent: types.VmCloneEvent{
 			VmEvent: event,
 		},
-		DestFolder: folder.eventArgument(),
+		DestFolder: folderEventArgument(folder),
 		DestName:   req.Name,
 		DestHost:   *host.eventArgument(),
 	})
@@ -1652,7 +1658,7 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
 			})
 		}
 
-		res := folder.CreateVMTask(ctx, &types.CreateVM_Task{
+		res := Map.Get(req.Folder).(vmFolder).CreateVMTask(ctx, &types.CreateVM_Task{
 			This:   folder.Self,
 			Config: config,
 			Pool:   *pool,
@@ -1690,7 +1696,7 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
 	}
 }
 
-func (vm *VirtualMachine) RelocateVMTask(req *types.RelocateVM_Task) soap.HasFault {
+func (vm *VirtualMachine) RelocateVMTask(ctx *Context, req *types.RelocateVM_Task) soap.HasFault {
 	task := CreateTask(vm, "relocateVm", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		var changes []types.PropertyChange
 
@@ -1722,7 +1728,7 @@ func (vm *VirtualMachine) RelocateVMTask(req *types.RelocateVM_Task) soap.HasFau
 
 		if ref := req.Spec.Folder; ref != nil {
 			folder := Map.Get(*ref).(*Folder)
-			folder.MoveIntoFolderTask(&types.MoveIntoFolder_Task{
+			folder.MoveIntoFolderTask(ctx, &types.MoveIntoFolder_Task{
 				List: []types.ManagedObjectReference{vm.Self},
 			})
 		}


Test patch:
diff --git a/simulator/entity_test.go b/simulator/entity_test.go
index 00e18e6e..0e9d391a 100644
--- a/simulator/entity_test.go
+++ b/simulator/entity_test.go
@@ -43,7 +43,7 @@ func TestRename(t *testing.T) {
 
 	f1 := Map.Get(vmFolder.ChildEntity[0]).(*Folder) // "F1"
 
-	id := vmFolder.CreateFolder(&types.CreateFolder{
+	id := vmFolder.CreateFolder(internalContext, &types.CreateFolder{
 		This: vmFolder.Reference(),
 		Name: "F2",
 	}).(*methods.CreateFolderBody).Res.Returnval
diff --git a/simulator/folder_test.go b/simulator/folder_test.go
index 6eafa264..56a3a859 100644
--- a/simulator/folder_test.go
+++ b/simulator/folder_test.go
@@ -218,7 +218,7 @@ func TestFolderFaults(t *testing.T) {
 	f := Folder{}
 	f.ChildType = []string{"VirtualMachine"}
 
-	if f.CreateFolder(nil).Fault() == nil {
+	if f.CreateFolder(nil, nil).Fault() == nil {
 		t.Error("expected fault")
 	}
 
diff --git a/simulator/property_collector_test.go b/simulator/property_collector_test.go
index 02d31328..dbfb596c 100644
--- a/simulator/property_collector_test.go
+++ b/simulator/property_collector_test.go
@@ -801,7 +801,7 @@ func TestPropertyCollectorFold(t *testing.T) {
 
 func TestPropertyCollectorInvalidSpecName(t *testing.T) {
 	obj := Map.Put(new(Folder))
-	obj.(*Folder).putChild(new(Folder))
+	folderPutChild(internalContext, &obj.(*Folder).Folder, new(Folder))
 
 	pc := &PropertyCollector{}