Repository: vmware/govmomi
Pull request #2654 (2021-11-11 01:48:35)

Support Creating/Reconfiguring a simulator VM with VApp properties
Currently, the simulator ignores any VApp properties in the VM
creation and Reconfigure workflows.

This change enables users to instantiate or Reconfigure a simulator VM
with VApp properties set. This commit also adds tests to validate the
workflows.

Closes: #2642

## Description

Please include a summary of the change and which issue is fixed. Please also
include relevant motivation and context. List any dependencies that are required
for this change.

Closes: #(issue-number)

## Type of change

Please mark options that are relevant:

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to
  not work as expected)
- [ ] This change requires a documentation update
- [ ] Build related change

## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. If applicable, please also list any relevant
details for your test configuration.

- [x] Added unit tests to validate the change

## Checklist:

- [x] My code follows the CONTRIBUTION [guidelines](./../CONTRIBUTING.md) of
  this project
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged


Patch:
diff --git a/Makefile b/Makefile
index 1a89322a..1be0f391 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@
 # SPDX-License-Identifier: Apache-2.0
 
 # If you update this file, please follow
-# https://www.thapaliya.com/en/writings/well-documented-makefiles/
+# https://suva.sh/posts/well-documented-makefiles
 
 # Ensure Make is run with bash shell as some syntax below is bash-specific
 SHELL := /usr/bin/env bash
diff --git a/eam/simulator/agent.go b/eam/simulator/agent.go
index 8849ae16..d055cc9d 100644
--- a/eam/simulator/agent.go
+++ b/eam/simulator/agent.go
@@ -60,6 +60,7 @@ func NewAgent(
 	config types.AgentConfigInfo,
 	vmName string,
 	vmPlacement AgentVMPlacementOptions) (*Agent, vim.BaseMethodFault) {
+
 	vimMap := simulator.Map
 
 	agent := &Agent{
@@ -85,9 +86,6 @@ func NewAgent(
 	// receiving API calls from clients.
 	ctx.Map.Put(agent)
 
-	// simulator.VirtualMachine related calls need the vimMap (aka global Map)
-	vimCtx := simulator.SpoofContext()
-
 	createVm := func() (vim.ManagedObjectReference, *vim.LocalizedMethodFault) {
 		var vmRef vim.ManagedObjectReference
 
@@ -129,13 +127,13 @@ func NewAgent(
 
 		// Create the VM for this agent.
 		vmFolder := vimMap.Get(vmPlacement.folder).(*simulator.Folder)
-		createVmTaskRef := vmFolder.CreateVMTask(vimCtx, &vim.CreateVM_Task{
+		createVmTaskRef := vmFolder.CreateVMTask(ctx, &vim.CreateVM_Task{
 			This:   vmFolder.Self,
 			Config: vmConfigSpec,
 			Pool:   vmPlacement.pool,
 			Host:   &vmPlacement.host,
 		}).(*vimmethods.CreateVM_TaskBody).Res.Returnval
-		createVmTask := simulator.Map.Get(createVmTaskRef).(*simulator.Task)
+		createVmTask := vimMap.Get(createVmTaskRef).(*simulator.Task)
 
 		// Wait for the task to complete and see if there is an error.
 		createVmTask.Wait()
diff --git a/object/tenant_manager.go b/object/tenant_manager.go
deleted file mode 100644
index 4dda196e..00000000
--- a/object/tenant_manager.go
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
-Copyright (c) 2021 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package object
-
-import (
-	"context"
-
-	"github.com/vmware/govmomi/vim25"
-	"github.com/vmware/govmomi/vim25/methods"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-type TenantManager struct {
-	Common
-}
-
-func NewTenantManager(c *vim25.Client) *TenantManager {
-	t := TenantManager{
-		Common: NewCommon(c, *c.ServiceContent.TenantManager),
-	}
-
-	return &t
-}
-
-func (t TenantManager) MarkServiceProviderEntities(ctx context.Context, entities []types.ManagedObjectReference) error {
-	req := types.MarkServiceProviderEntities{
-		This:   t.Reference(),
-		Entity: entities,
-	}
-
-	_, err := methods.MarkServiceProviderEntities(ctx, t.Client(), &req)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (t TenantManager) UnmarkServiceProviderEntities(ctx context.Context, entities []types.ManagedObjectReference) error {
-	req := types.UnmarkServiceProviderEntities{
-		This:   t.Reference(),
-		Entity: entities,
-	}
-
-	_, err := methods.UnmarkServiceProviderEntities(ctx, t.Client(), &req)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (t TenantManager) RetrieveServiceProviderEntities(ctx context.Context) ([]types.ManagedObjectReference, error) {
-	req := types.RetrieveServiceProviderEntities{
-		This: t.Reference(),
-	}
-
-	res, err := methods.RetrieveServiceProviderEntities(ctx, t.Client(), &req)
-	if err != nil {
-		return nil, err
-	}
-
-	return res.Returnval, nil
-}
diff --git a/simulator/cluster_compute_resource.go b/simulator/cluster_compute_resource.go
index 20df21c6..3c2d52c9 100644
--- a/simulator/cluster_compute_resource.go
+++ b/simulator/cluster_compute_resource.go
@@ -58,7 +58,7 @@ func (add *addHost) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 	host.configure(spec, add.req.AsConnected)
 
 	cr := add.ClusterComputeResource
-	task.ctx.Map.PutEntity(cr, task.ctx.Map.NewEntity(host))
+	Map.PutEntity(cr, Map.NewEntity(host))
 	host.Summary.Host = &host.Self
 
 	cr.Host = append(cr.Host, host.Reference())
@@ -398,7 +398,7 @@ func (c *ClusterComputeResource) PlaceVm(ctx *Context, req *types.PlaceVm) soap.
 }
 
 func CreateClusterComputeResource(ctx *Context, f *Folder, name string, spec types.ClusterConfigSpecEx) (*ClusterComputeResource, types.BaseMethodFault) {
-	if e := ctx.Map.FindByName(name, f.ChildEntity); e != nil {
+	if e := Map.FindByName(name, f.ChildEntity); e != nil {
 		return nil, &types.DuplicateName{
 			Name:   e.Entity().Name,
 			Object: e.Reference(),
@@ -408,7 +408,7 @@ func CreateClusterComputeResource(ctx *Context, f *Folder, name string, spec typ
 	cluster := &ClusterComputeResource{}
 	cluster.EnvironmentBrowser = newEnvironmentBrowser()
 	cluster.Name = name
-	cluster.Network = ctx.Map.getEntityDatacenter(f).defaultNetwork()
+	cluster.Network = Map.getEntityDatacenter(f).defaultNetwork()
 	cluster.Summary = &types.ClusterComputeResourceSummary{
 		UsageSummary: new(types.ClusterUsageSummary),
 	}
@@ -420,7 +420,7 @@ func CreateClusterComputeResource(ctx *Context, f *Folder, name string, spec typ
 	config.DrsConfig.Enabled = types.NewBool(true)
 
 	pool := NewResourcePool()
-	ctx.Map.PutEntity(cluster, ctx.Map.NewEntity(pool))
+	Map.PutEntity(cluster, Map.NewEntity(pool))
 	cluster.ResourcePool = &pool.Self
 
 	folderPutChild(ctx, &f.Folder, cluster)
diff --git a/simulator/custom_fields_manager.go b/simulator/custom_fields_manager.go
index 049a3ed3..c26c5fc9 100644
--- a/simulator/custom_fields_manager.go
+++ b/simulator/custom_fields_manager.go
@@ -32,7 +32,7 @@ type CustomFieldsManager struct {
 // Iterates through all entities of passed field type;
 // Removes found field from their custom field properties.
 func entitiesFieldRemove(ctx *Context, field types.CustomFieldDef) {
-	entities := ctx.Map.All(field.ManagedObjectType)
+	entities := Map.All(field.ManagedObjectType)
 	for _, e := range entities {
 		entity := e.Entity()
 		ctx.WithLock(entity, func() {
@@ -66,7 +66,7 @@ func entitiesFieldRemove(ctx *Context, field types.CustomFieldDef) {
 // Iterates through all entities of passed field type;
 // Renames found field in entity's AvailableField property.
 func entitiesFieldRename(ctx *Context, field types.CustomFieldDef) {
-	entities := ctx.Map.All(field.ManagedObjectType)
+	entities := Map.All(field.ManagedObjectType)
 	for _, e := range entities {
 		entity := e.Entity()
 		ctx.WithLock(entity, func() {
@@ -123,7 +123,7 @@ func (c *CustomFieldsManager) AddCustomFieldDef(ctx *Context, req *types.AddCust
 		FieldInstancePrivileges: req.FieldPolicy,
 	}
 
-	entities := ctx.Map.All(req.MoType)
+	entities := Map.All(req.MoType)
 	for _, e := range entities {
 		entity := e.Entity()
 		ctx.WithLock(entity, func() {
@@ -188,7 +188,7 @@ func (c *CustomFieldsManager) SetField(ctx *Context, req *types.SetField) soap.H
 		Value:            req.Value,
 	}
 
-	entity := ctx.Map.Get(req.Entity).(mo.Entity).Entity()
+	entity := Map.Get(req.Entity).(mo.Entity).Entity()
 	ctx.WithLock(entity, func() {
 		entity.CustomValue = append(entity.CustomValue, newValue)
 		entity.Value = append(entity.Value, newValue)
diff --git a/simulator/customization_spec_manager.go b/simulator/customization_spec_manager.go
index b522680e..4711864b 100644
--- a/simulator/customization_spec_manager.go
+++ b/simulator/customization_spec_manager.go
@@ -28,7 +28,7 @@ import (
 )
 
 var DefaultCustomizationSpec = []types.CustomizationSpecItem{
-	{
+	types.CustomizationSpecItem{
 		Info: types.CustomizationSpecInfo{
 			Name:           "vcsim-linux",
 			Description:    "",
@@ -68,7 +68,7 @@ var DefaultCustomizationSpec = []types.CustomizationSpecItem{
 			EncryptionKey: nil,
 		},
 	},
-	{
+	types.CustomizationSpecItem{
 		Info: types.CustomizationSpecInfo{
 			Name:           "vcsim-linux-static",
 			Description:    "",
@@ -111,7 +111,7 @@ var DefaultCustomizationSpec = []types.CustomizationSpecItem{
 			EncryptionKey: nil,
 		},
 	},
-	{
+	types.CustomizationSpecItem{
 		Info: types.CustomizationSpecInfo{
 			Name:           "vcsim-windows-static",
 			Description:    "",
@@ -172,7 +172,7 @@ var DefaultCustomizationSpec = []types.CustomizationSpecItem{
 			EncryptionKey: []uint8{0x30},
 		},
 	},
-	{
+	types.CustomizationSpecItem{
 		Info: types.CustomizationSpecInfo{
 			Name:           "vcsim-windows-domain",
 			Description:    "",
diff --git a/simulator/datacenter.go b/simulator/datacenter.go
index 41fc5696..1ab6df47 100644
--- a/simulator/datacenter.go
+++ b/simulator/datacenter.go
@@ -77,10 +77,10 @@ func (dc *Datacenter) createFolders(ctx *Context) {
 		if dc.isESX {
 			folder.ChildType = f.types[:1]
 			folder.Self = *f.ref
-			ctx.Map.PutEntity(dc, folder)
+			Map.PutEntity(dc, folder)
 		} else {
 			folder.ChildType = f.types
-			e := ctx.Map.PutEntity(dc, folder)
+			e := Map.PutEntity(dc, folder)
 
 			// propagate the generated morefs to Datacenter
 			ref := e.Reference()
@@ -89,7 +89,7 @@ func (dc *Datacenter) createFolders(ctx *Context) {
 		}
 	}
 
-	net := ctx.Map.Get(dc.NetworkFolder).(*Folder)
+	net := Map.Get(dc.NetworkFolder).(*Folder)
 
 	for _, ref := range esx.Datacenter.Network {
 		// Add VM Network by default to each Datacenter
@@ -162,22 +162,11 @@ func (dc *Datacenter) PowerOnMultiVMTask(ctx *Context, req *types.PowerOnMultiVM
 		res.Attempted = []types.ClusterAttemptedVmInfo{}
 
 		for _, ref := range req.Vm {
-			vm := ctx.Map.Get(ref).(*VirtualMachine)
-
-			// This task creates multiple subtasks which violates the assumption
-			// of 1:1 Context:Task, which results in data races in objects
-			// like the Simulator.Event manager. This is the minimum context
-			// required for the PowerOnVMTask to complete.
-			taskCtx := &Context{
-				Context: ctx.Context,
-				Session: ctx.Session,
-				Map:     ctx.Map,
-			}
-
+			vm := Map.Get(ref).(*VirtualMachine)
 			// NOTE: Simulator does not actually perform any specific host-level placement
 			// (equivalent to vSphere DRS).
-			taskCtx.WithLock(vm, func() {
-				vmTaskBody := vm.PowerOnVMTask(taskCtx, &types.PowerOnVM_Task{}).(*methods.PowerOnVM_TaskBody)
+			ctx.WithLock(vm, func() {
+				vmTaskBody := vm.PowerOnVMTask(ctx, &types.PowerOnVM_Task{}).(*methods.PowerOnVM_TaskBody)
 				res.Attempted = append(res.Attempted, types.ClusterAttemptedVmInfo{Vm: ref, Task: &vmTaskBody.Res.Returnval})
 			})
 		}
@@ -200,13 +189,13 @@ func (d *Datacenter) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.Has
 		}
 
 		for _, ref := range folders {
-			f, _ := asFolderMO(ctx.Map.Get(ref))
+			f, _ := asFolderMO(Map.Get(ref))
 			if len(f.ChildEntity) != 0 {
 				return nil, &types.ResourceInUse{}
 			}
 		}
 
-		p, _ := asFolderMO(ctx.Map.Get(*d.Parent))
+		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 f341bdd1..149827fd 100644
--- a/simulator/datastore.go
+++ b/simulator/datastore.go
@@ -96,13 +96,13 @@ func (ds *Datastore) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.Has
 		}
 
 		for _, mount := range ds.Host {
-			host := ctx.Map.Get(mount.Key).(*HostSystem)
-			ctx.Map.RemoveReference(ctx, host, &host.Datastore, ds.Self)
+			host := Map.Get(mount.Key).(*HostSystem)
+			Map.RemoveReference(ctx, host, &host.Datastore, ds.Self)
 			parent := hostParent(&host.HostSystem)
-			ctx.Map.RemoveReference(ctx, parent, &parent.Datastore, ds.Self)
+			Map.RemoveReference(ctx, parent, &parent.Datastore, ds.Self)
 		}
 
-		p, _ := asFolderMO(ctx.Map.Get(*ds.Parent))
+		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 05216ef0..8894b854 100644
--- a/simulator/dvs.go
+++ b/simulator/dvs.go
@@ -37,7 +37,7 @@ type DistributedVirtualSwitch struct {
 
 func (s *DistributedVirtualSwitch) AddDVPortgroupTask(ctx *Context, c *types.AddDVPortgroup_Task) soap.HasFault {
 	task := CreateTask(s, "addDVPortgroup", func(t *Task) (types.AnyType, types.BaseMethodFault) {
-		f := ctx.Map.getEntityParent(s, "Folder").(*Folder)
+		f := Map.getEntityParent(s, "Folder").(*Folder)
 
 		portgroups := s.Portgroup
 		portgroupNames := s.Summary.PortgroupName
@@ -58,7 +58,7 @@ func (s *DistributedVirtualSwitch) AddDVPortgroupTask(ctx *Context, c *types.Add
 				}
 
 			} else {
-				if obj := ctx.Map.FindByName(pg.Name, f.ChildEntity); obj != nil {
+				if obj := Map.FindByName(pg.Name, f.ChildEntity); obj != nil {
 					return nil, &types.DuplicateName{
 						Name:   pg.Name,
 						Object: obj.Reference(),
@@ -143,18 +143,18 @@ func (s *DistributedVirtualSwitch) AddDVPortgroupTask(ctx *Context, c *types.Add
 			for _, h := range s.Summary.HostMember {
 				pg.Host = append(pg.Host, h)
 
-				host := ctx.Map.Get(h).(*HostSystem)
-				ctx.Map.AppendReference(ctx, host, &host.Network, pg.Reference())
+				host := Map.Get(h).(*HostSystem)
+				Map.AppendReference(ctx, host, &host.Network, pg.Reference())
 
-				parent := ctx.Map.Get(*host.HostSystem.Parent)
+				parent := Map.Get(*host.HostSystem.Parent)
 				computeNetworks := append(hostParent(&host.HostSystem).Network, pg.Reference())
-				ctx.Map.Update(parent, []types.PropertyChange{
+				Map.Update(parent, []types.PropertyChange{
 					{Name: "network", Val: computeNetworks},
 				})
 			}
 		}
 
-		ctx.Map.Update(s, []types.PropertyChange{
+		Map.Update(s, []types.PropertyChange{
 			{Name: "portgroup", Val: portgroups},
 			{Name: "summary.portgroupName", Val: portgroupNames},
 		})
@@ -176,7 +176,7 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(ctx *Context, req *types.R
 		members := s.Summary.HostMember
 
 		for _, member := range spec.Host {
-			h := ctx.Map.Get(member.Host)
+			h := Map.Get(member.Host)
 			if h == nil {
 				return nil, &types.ManagedObjectNotFound{Obj: member.Host}
 			}
@@ -190,26 +190,26 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(ctx *Context, req *types.R
 				}
 
 				hostNetworks := append(host.Network, s.Portgroup...)
-				ctx.Map.Update(host, []types.PropertyChange{
+				Map.Update(host, []types.PropertyChange{
 					{Name: "network", Val: hostNetworks},
 				})
 				members = append(members, member.Host)
-				parent := ctx.Map.Get(*host.HostSystem.Parent)
+				parent := Map.Get(*host.HostSystem.Parent)
 
 				var pgs []types.ManagedObjectReference
 				for _, ref := range s.Portgroup {
-					pg := ctx.Map.Get(ref).(*DistributedVirtualPortgroup)
+					pg := Map.Get(ref).(*DistributedVirtualPortgroup)
 					pgs = append(pgs, ref)
 
 					pgHosts := append(pg.Host, member.Host)
-					ctx.Map.Update(pg, []types.PropertyChange{
+					Map.Update(pg, []types.PropertyChange{
 						{Name: "host", Val: pgHosts},
 					})
 
 					cr := hostParent(&host.HostSystem)
 					if FindReference(cr.Network, ref) == nil {
 						computeNetworks := append(cr.Network, ref)
-						ctx.Map.Update(parent, []types.PropertyChange{
+						Map.Update(parent, []types.PropertyChange{
 							{Name: "network", Val: computeNetworks},
 						})
 					}
@@ -217,7 +217,7 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(ctx *Context, req *types.R
 
 			case types.ConfigSpecOperationRemove:
 				for _, ref := range host.Vm {
-					vm := ctx.Map.Get(ref).(*VirtualMachine)
+					vm := Map.Get(ref).(*VirtualMachine)
 					if pg := FindReference(vm.Network, s.Portgroup...); pg != nil {
 						return nil, &types.ResourceInUse{
 							Type: pg.Type,
@@ -232,7 +232,7 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(ctx *Context, req *types.R
 			}
 		}
 
-		ctx.Map.Update(s, []types.PropertyChange{
+		Map.Update(s, []types.PropertyChange{
 			{Name: "summary.hostMember", Val: members},
 		})
 
@@ -256,7 +256,7 @@ func (s *DistributedVirtualSwitch) FetchDVPorts(req *types.FetchDVPorts) soap.Ha
 
 func (s *DistributedVirtualSwitch) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault {
 	task := CreateTask(s, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) {
-		f := ctx.Map.getEntityParent(s, "Folder").(*Folder)
+		f := Map.getEntityParent(s, "Folder").(*Folder)
 		folderRemoveChild(ctx, &f.Folder, s.Reference())
 		return nil, nil
 	})
diff --git a/simulator/entity.go b/simulator/entity.go
index 183e954f..5178da5a 100644
--- a/simulator/entity.go
+++ b/simulator/entity.go
@@ -25,16 +25,16 @@ import (
 
 func RenameTask(ctx *Context, e mo.Entity, r *types.Rename_Task, dup ...bool) soap.HasFault {
 	task := CreateTask(e, "rename", func(t *Task) (types.AnyType, types.BaseMethodFault) {
-		obj := ctx.Map.Get(r.This).(mo.Entity).Entity()
+		obj := Map.Get(r.This).(mo.Entity).Entity()
 
 		canDup := len(dup) == 1 && dup[0]
-		if parent, ok := asFolderMO(ctx.Map.Get(*obj.Parent)); ok && !canDup {
-			if ctx.Map.FindByName(r.NewName, parent.ChildEntity) != nil {
+		if parent, ok := asFolderMO(Map.Get(*obj.Parent)); ok && !canDup {
+			if Map.FindByName(r.NewName, parent.ChildEntity) != nil {
 				return nil, &types.InvalidArgument{InvalidProperty: "name"}
 			}
 		}
 
-		ctx.Map.Update(e, []types.PropertyChange{{Name: "name", Val: r.NewName}})
+		Map.Update(e, []types.PropertyChange{{Name: "name", Val: r.NewName}})
 
 		return nil, nil
 	})
diff --git a/simulator/event_manager.go b/simulator/event_manager.go
index 329a761e..91f1d209 100644
--- a/simulator/event_manager.go
+++ b/simulator/event_manager.go
@@ -39,11 +39,9 @@ var (
 type EventManager struct {
 	mo.EventManager
 
-	root types.ManagedObjectReference
-
-	history *list.List
-	key     int32
-
+	root       types.ManagedObjectReference
+	history    *list.List
+	key        int32
 	collectors map[types.ManagedObjectReference]*EventHistoryCollector
 	templates  map[string]*template.Template
 }
diff --git a/simulator/folder.go b/simulator/folder.go
index a329112f..bccea4d6 100644
--- a/simulator/folder.go
+++ b/simulator/folder.go
@@ -64,7 +64,7 @@ func folderUpdate(ctx *Context, f *mo.Folder, o mo.Reference, u func(*Context, m
 		return // nothing to update
 	}
 
-	dc := ctx.Map.getEntityDatacenter(f)
+	dc := Map.getEntityDatacenter(f)
 
 	switch ref.Type {
 	case "Network", "DistributedVirtualSwitch", "DistributedVirtualPortgroup":
@@ -88,10 +88,10 @@ func networkSummary(n *mo.Network) types.BaseNetworkSummary {
 func folderPutChild(ctx *Context, f *mo.Folder, o mo.Entity) {
 	ctx.WithLock(f, func() {
 		// Need to update ChildEntity before Map.Put for ContainerView updates to work properly
-		f.ChildEntity = append(f.ChildEntity, ctx.Map.reference(o))
-		ctx.Map.PutEntity(f, o)
+		f.ChildEntity = append(f.ChildEntity, Map.reference(o))
+		Map.PutEntity(f, o)
 
-		folderUpdate(ctx, f, o, ctx.Map.AddReference)
+		folderUpdate(ctx, f, o, Map.AddReference)
 
 		ctx.WithLock(o, func() {
 			switch e := o.(type) {
@@ -107,12 +107,12 @@ func folderPutChild(ctx *Context, f *mo.Folder, o mo.Entity) {
 }
 
 func folderRemoveChild(ctx *Context, f *mo.Folder, o mo.Reference) {
-	ctx.Map.Remove(ctx, o.Reference())
+	Map.Remove(ctx, o.Reference())
 
 	ctx.WithLock(f, func() {
 		RemoveReference(&f.ChildEntity, o.Reference())
 
-		folderUpdate(ctx, f, o, ctx.Map.RemoveReference)
+		folderUpdate(ctx, f, o, Map.RemoveReference)
 	})
 }
 
@@ -198,7 +198,7 @@ func (f *Folder) CreateFolder(ctx *Context, c *types.CreateFolder) soap.HasFault
 	r := &methods.CreateFolderBody{}
 
 	if folderHasChildType(&f.Folder, "Folder") {
-		if obj := ctx.Map.FindByName(c.Name, f.ChildEntity); obj != nil {
+		if obj := Map.FindByName(c.Name, f.ChildEntity); obj != nil {
 			r.Fault_ = Fault("", &types.DuplicateName{
 				Name:   c.Name,
 				Object: f.Self,
@@ -233,7 +233,7 @@ func (f *Folder) CreateStoragePod(ctx *Context, c *types.CreateStoragePod) soap.
 	r := &methods.CreateStoragePodBody{}
 
 	if folderHasChildType(&f.Folder, "StoragePod") {
-		if obj := ctx.Map.FindByName(c.Name, f.ChildEntity); obj != nil {
+		if obj := Map.FindByName(c.Name, f.ChildEntity); obj != nil {
 			r.Fault_ = Fault("", &types.DuplicateName{
 				Name:   c.Name,
 				Object: f.Self,
@@ -266,7 +266,7 @@ func (p *StoragePod) MoveIntoFolderTask(ctx *Context, c *types.MoveIntoFolder_Ta
 	task := CreateTask(p, "moveIntoFolder", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		f := &Folder{Folder: p.Folder}
 		id := f.MoveIntoFolderTask(ctx, c).(*methods.MoveIntoFolder_TaskBody).Res.Returnval
-		ftask := ctx.Map.Get(id).(*Task)
+		ftask := Map.Get(id).(*Task)
 		ftask.Wait()
 		if ftask.Info.Error != nil {
 			return nil, ftask.Info.Error.Fault
@@ -287,7 +287,7 @@ func (f *Folder) CreateDatacenter(ctx *Context, c *types.CreateDatacenter) soap.
 	if folderHasChildType(&f.Folder, "Datacenter") && folderHasChildType(&f.Folder, "Folder") {
 		dc := NewDatacenter(ctx, &f.Folder)
 
-		ctx.Map.Update(dc, []types.PropertyChange{
+		Map.Update(dc, []types.PropertyChange{
 			{Name: "name", Val: c.Name},
 		})
 
@@ -365,8 +365,8 @@ func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 	vm.ResourcePool = &c.req.Pool
 
 	if c.req.Host == nil {
-		pool := c.ctx.Map.Get(c.req.Pool).(mo.Entity)
-		cr := c.ctx.Map.getEntityComputeResource(pool)
+		pool := Map.Get(c.req.Pool).(mo.Entity)
+		cr := Map.getEntityComputeResource(pool)
 
 		c.ctx.WithLock(cr, func() {
 			var hosts []types.ManagedObjectReference
@@ -403,16 +403,16 @@ func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 		return nil, err
 	}
 
-	host := c.ctx.Map.Get(*vm.Runtime.Host).(*HostSystem)
-	c.ctx.Map.AppendReference(c.ctx, host, &host.Vm, vm.Self)
+	host := Map.Get(*vm.Runtime.Host).(*HostSystem)
+	Map.AppendReference(c.ctx, host, &host.Vm, vm.Self)
 	vm.EnvironmentBrowser = *hostParent(&host.HostSystem).EnvironmentBrowser
 
 	for i := range vm.Datastore {
-		ds := c.ctx.Map.Get(vm.Datastore[i]).(*Datastore)
-		c.ctx.Map.AppendReference(c.ctx, ds, &ds.Vm, vm.Self)
+		ds := Map.Get(vm.Datastore[i]).(*Datastore)
+		Map.AppendReference(c.ctx, ds, &ds.Vm, vm.Self)
 	}
 
-	pool := c.ctx.Map.Get(*vm.ResourcePool)
+	pool := Map.Get(*vm.ResourcePool)
 	// This can be an internal call from VirtualApp.CreateChildVMTask, where pool is already locked.
 	c.ctx.WithLock(pool, func() {
 		if rp, ok := asResourcePoolMO(pool); ok {
@@ -444,7 +444,7 @@ func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 
 	vm.RefreshStorageInfo(c.ctx, nil)
 
-	c.ctx.Map.Update(vm, []types.PropertyChange{
+	Map.Update(vm, []types.PropertyChange{
 		{Name: "name", Val: c.req.Config.Name},
 	})
 
@@ -477,7 +477,7 @@ func (c *registerVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 			return nil, &types.InvalidArgument{InvalidProperty: "pool"}
 		}
 
-		pool = hostParent(&c.ctx.Map.Get(*host).(*HostSystem).HostSystem).ResourcePool
+		pool = hostParent(&Map.Get(*host).(*HostSystem).HostSystem).ResourcePool
 	} else {
 		if pool == nil {
 			return nil, &types.InvalidArgument{InvalidProperty: "pool"}
@@ -488,11 +488,11 @@ func (c *registerVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 		return nil, &types.InvalidArgument{InvalidProperty: "path"}
 	}
 
-	s := c.ctx.Map.SearchIndex()
+	s := Map.SearchIndex()
 	r := s.FindByDatastorePath(&types.FindByDatastorePath{
 		This:       s.Reference(),
 		Path:       c.req.Path,
-		Datacenter: c.ctx.Map.getEntityDatacenter(c.Folder).Reference(),
+		Datacenter: Map.getEntityDatacenter(c.Folder).Reference(),
 	})
 
 	if ref := r.(*methods.FindByDatastorePathBody).Res.Returnval; ref != nil {
@@ -545,9 +545,9 @@ func (f *Folder) RegisterVMTask(ctx *Context, c *types.RegisterVM_Task) soap.Has
 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 := ctx.Map.Get(ref).(mo.Entity)
+			obj := Map.Get(ref).(mo.Entity)
 
-			parent, ok := ctx.Map.Get(*(obj.Entity()).Parent).(*Folder)
+			parent, ok := Map.Get(*(obj.Entity()).Parent).(*Folder)
 
 			if !ok || !folderHasChildType(&f.Folder, ref.Type) {
 				return nil, &types.NotSupported{}
@@ -574,7 +574,7 @@ func (f *Folder) CreateDVSTask(ctx *Context, req *types.CreateDVS_Task) soap.Has
 		dvs.Name = spec.Name
 		dvs.Entity().Name = dvs.Name
 
-		if ctx.Map.FindByName(dvs.Name, f.ChildEntity) != nil {
+		if Map.FindByName(dvs.Name, f.ChildEntity) != nil {
 			return nil, &types.InvalidArgument{InvalidProperty: "name"}
 		}
 
@@ -618,7 +618,7 @@ func (f *Folder) CreateDVSTask(ctx *Context, req *types.CreateDVS_Task) soap.Has
 		dvs.Config = configInfo
 
 		if dvs.Summary.ProductInfo == nil {
-			product := ctx.Map.content().About
+			product := Map.content().About
 			dvs.Summary.ProductInfo = &types.DistributedVirtualSwitchProductSpec{
 				Name:            "DVS",
 				Vendor:          product.Vendor,
@@ -677,7 +677,7 @@ func (f *Folder) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFaul
 	task := CreateTask(f, "destroy", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		// Attempt to destroy all children
 		for _, c := range f.ChildEntity {
-			obj, ok := ctx.Map.Get(c).(destroyer)
+			obj, ok := Map.Get(c).(destroyer)
 			if !ok {
 				continue
 			}
@@ -688,7 +688,7 @@ func (f *Folder) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFaul
 					This: c,
 				}).(*methods.Destroy_TaskBody).Res.Returnval
 
-				t := ctx.Map.Get(id).(*Task)
+				t := Map.Get(id).(*Task)
 				t.Wait()
 				if t.Info.Error != nil {
 					fault = t.Info.Error.Fault // For example, can't destroy a powered on VM
@@ -700,7 +700,7 @@ func (f *Folder) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFaul
 		}
 
 		// Remove the folder itself
-		folderRemoveChild(ctx, &ctx.Map.Get(*f.Parent).(*Folder).Folder, 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 a7f6780e..eeac9481 100644
--- a/simulator/host_datastore_system.go
+++ b/simulator/host_datastore_system.go
@@ -38,7 +38,7 @@ func (dss *HostDatastoreSystem) add(ctx *Context, ds *Datastore) *soap.Fault {
 
 	info.Name = ds.Name
 
-	if e := ctx.Map.FindByName(ds.Name, dss.Datastore); e != nil {
+	if e := Map.FindByName(ds.Name, dss.Datastore); e != nil {
 		return Fault(e.Reference().Value, &types.DuplicateName{
 			Name:   ds.Name,
 			Object: e.Reference(),
@@ -59,7 +59,7 @@ func (dss *HostDatastoreSystem) add(ctx *Context, ds *Datastore) *soap.Fault {
 		}
 	}
 
-	folder := ctx.Map.getEntityFolder(dss.Host, "datastore")
+	folder := Map.getEntityFolder(dss.Host, "datastore")
 	ds.Self.Type = typeName(ds)
 	// Datastore is the only type where create methods do not include the parent (Folder in this case),
 	// but we need the moref to be unique per DC/datastoreFolder, but not per-HostSystem.
@@ -82,12 +82,12 @@ func (dss *HostDatastoreSystem) add(ctx *Context, ds *Datastore) *soap.Fault {
 	dss.Datastore = append(dss.Datastore, ds.Self)
 	dss.Host.Datastore = dss.Datastore
 	parent := hostParent(dss.Host)
-	ctx.Map.AddReference(ctx, parent, &parent.Datastore, ds.Self)
+	Map.AddReference(ctx, parent, &parent.Datastore, ds.Self)
 
-	if ctx.Map.Get(ds.Self) == nil {
+	if Map.Get(ds.Self) == nil {
 		browser := &HostDatastoreBrowser{}
 		browser.Datastore = dss.Datastore
-		ds.Browser = ctx.Map.Put(browser).Reference()
+		ds.Browser = Map.Put(browser).Reference()
 
 		ds.Summary.Capacity = int64(units.TB * 10)
 		ds.Summary.FreeSpace = ds.Summary.Capacity
diff --git a/simulator/host_network_system.go b/simulator/host_network_system.go
index 286ca5b3..017837f6 100644
--- a/simulator/host_network_system.go
+++ b/simulator/host_network_system.go
@@ -126,7 +126,7 @@ func (s *HostNetworkSystem) AddPortGroup(ctx *Context, c *types.AddPortGroup) so
 
 	folder := s.folder()
 
-	if obj := ctx.Map.FindByName(c.Portgrp.Name, folder.ChildEntity); obj != nil {
+	if obj := Map.FindByName(c.Portgrp.Name, folder.ChildEntity); obj != nil {
 		r.Fault_ = Fault("", &types.DuplicateName{
 			Name:   c.Portgrp.Name,
 			Object: obj.Reference(),
@@ -170,7 +170,7 @@ func (s *HostNetworkSystem) RemovePortGroup(ctx *Context, c *types.RemovePortGro
 	}
 
 	folder := s.folder()
-	e := ctx.Map.FindByName(c.PgName, folder.ChildEntity)
+	e := Map.FindByName(c.PgName, folder.ChildEntity)
 	folderRemoveChild(ctx, &folder.Folder, e.Reference())
 
 	for i, pg := range s.NetworkInfo.Portgroup {
diff --git a/simulator/host_system.go b/simulator/host_system.go
index 809d2e5d..b1591181 100644
--- a/simulator/host_system.go
+++ b/simulator/host_system.go
@@ -172,14 +172,14 @@ func CreateDefaultESX(ctx *Context, f *Folder) {
 	cr.Name = host.Name
 	cr.Host = append(cr.Host, host.Reference())
 	host.Network = cr.Network
-	ctx.Map.PutEntity(cr, host)
+	Map.PutEntity(cr, host)
 
 	pool := NewResourcePool()
 	cr.ResourcePool = &pool.Self
-	ctx.Map.PutEntity(cr, pool)
+	Map.PutEntity(cr, pool)
 	pool.Owner = cr.Self
 
-	folderPutChild(ctx, &ctx.Map.Get(dc.HostFolder).(*Folder).Folder, cr)
+	folderPutChild(ctx, &Map.Get(dc.HostFolder).(*Folder).Folder, cr)
 }
 
 // CreateStandaloneHost uses esx.HostSystem as a template, applying the given spec
@@ -204,13 +204,13 @@ func CreateStandaloneHost(ctx *Context, f *Folder, spec types.HostConnectSpec) (
 		EnvironmentBrowser: newEnvironmentBrowser(),
 	}
 
-	ctx.Map.PutEntity(cr, ctx.Map.NewEntity(host))
+	Map.PutEntity(cr, Map.NewEntity(host))
 	host.Summary.Host = &host.Self
 
-	ctx.Map.PutEntity(cr, ctx.Map.NewEntity(pool))
+	Map.PutEntity(cr, Map.NewEntity(pool))
 
 	cr.Name = host.Name
-	cr.Network = ctx.Map.getEntityDatacenter(f).defaultNetwork()
+	cr.Network = Map.getEntityDatacenter(f).defaultNetwork()
 	cr.Host = append(cr.Host, host.Reference())
 	cr.ResourcePool = &pool.Self
 
@@ -229,7 +229,7 @@ func (h *HostSystem) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.Has
 
 		ctx.postEvent(&types.HostRemovedEvent{HostEvent: h.event()})
 
-		f := ctx.Map.getEntityParent(h, "Folder").(*Folder)
+		f := Map.getEntityParent(h, "Folder").(*Folder)
 		folderRemoveChild(ctx, &f.Folder, h.Reference())
 
 		return nil, nil
diff --git a/simulator/model.go b/simulator/model.go
index 3083677b..8c3883d3 100644
--- a/simulator/model.go
+++ b/simulator/model.go
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2017-2021 VMware, Inc. All Rights Reserved.
+Copyright (c) 2017 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -257,7 +257,6 @@ var kinds = map[string]reflect.Type{
 	"StoragePod":                      reflect.TypeOf((*StoragePod)(nil)).Elem(),
 	"StorageResourceManager":          reflect.TypeOf((*StorageResourceManager)(nil)).Elem(),
 	"TaskManager":                     reflect.TypeOf((*TaskManager)(nil)).Elem(),
-	"TenantTenantManager":             reflect.TypeOf((*TenantManager)(nil)).Elem(),
 	"UserDirectory":                   reflect.TypeOf((*UserDirectory)(nil)).Elem(),
 	"VcenterVStorageObjectManager":    reflect.TypeOf((*VcenterVStorageObjectManager)(nil)).Elem(),
 	"ViewManager":                     reflect.TypeOf((*ViewManager)(nil)).Elem(),
@@ -308,18 +307,18 @@ func loadObject(content types.ObjectContent) (mo.Reference, error) {
 // 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(ctx *Context) error {
-	dc, ok := ctx.Map.Any("Datacenter").(*Datacenter)
+	dc, ok := Map.Any("Datacenter").(*Datacenter)
 	if !ok {
 		// Need to have at least 1 Datacenter
-		root := ctx.Map.Get(ctx.Map.content().RootFolder).(*Folder)
+		root := Map.Get(Map.content().RootFolder).(*Folder)
 		ref := root.CreateDatacenter(ctx, &types.CreateDatacenter{
 			This: root.Self,
 			Name: "DC0",
 		}).(*methods.CreateDatacenterBody).Res.Returnval
-		dc = ctx.Map.Get(ref).(*Datacenter)
+		dc = Map.Get(ref).(*Datacenter)
 	}
 
-	for ref, val := range ctx.Map.objects {
+	for ref, val := range Map.objects {
 		me, ok := val.(mo.Entity)
 		if !ok {
 			continue
@@ -328,7 +327,7 @@ func (m *Model) resolveReferences(ctx *Context) error {
 		if e.Parent == nil || ref.Type == "Folder" {
 			continue
 		}
-		if ctx.Map.Get(*e.Parent) == nil {
+		if Map.Get(*e.Parent) == nil {
 			// object was loaded without its parent, attempt to foster with another parent
 			switch e.Parent.Type {
 			case "Folder":
@@ -433,14 +432,12 @@ func (m *Model) Load(dir string) error {
 			s = new(ServiceInstance)
 			s.Self = content.Obj
 			Map = NewRegistry()
-			ctx.Map = Map
-			ctx.Map.Put(s)
+			Map.Put(s)
 			return mo.LoadObjectContent([]types.ObjectContent{content}, &s.ServiceInstance)
 		}
 
 		if s == nil {
 			s = NewServiceInstance(ctx, m.ServiceContent, m.RootFolder)
-			ctx.Map = Map
 		}
 
 		obj, err := loadObject(content)
@@ -454,7 +451,7 @@ func (m *Model) Load(dir string) error {
 			}
 		}
 
-		return m.loadMethod(ctx.Map.Put(obj), dir)
+		return m.loadMethod(Map.Put(obj), dir)
 	})
 
 	if err != nil {
@@ -470,7 +467,6 @@ func (m *Model) Load(dir string) error {
 func (m *Model) Create() error {
 	ctx := SpoofContext()
 	m.Service = New(NewServiceInstance(ctx, m.ServiceContent, m.RootFolder))
-	ctx.Map = Map
 
 	client := m.Service.client
 	root := object.NewRootFolder(client)
@@ -663,8 +659,8 @@ func (m *Model) Create() error {
 			// Use the 1st DVPG for the VMs eth0 backing
 			if npg == 0 {
 				// AddPortgroup_Task does not return the moid, so we look it up by name
-				net := ctx.Map.Get(folders.NetworkFolder.Reference()).(*Folder)
-				pg := ctx.Map.FindByName(name, net.ChildEntity)
+				net := Map.Get(folders.NetworkFolder.Reference()).(*Folder)
+				pg := Map.FindByName(name, net.ChildEntity)
 
 				vmnet, _ = object.NewDistributedVirtualPortgroup(client, pg.Reference()).EthernetCardBackingInfo(ctx)
 			}
@@ -688,7 +684,7 @@ func (m *Model) Create() error {
 		}
 
 		// Must use simulator methods directly for OpaqueNetwork
-		networkFolder := ctx.Map.Get(folders.NetworkFolder.Reference()).(*Folder)
+		networkFolder := Map.Get(folders.NetworkFolder.Reference()).(*Folder)
 
 		for i := 0; i < m.OpaqueNetwork; i++ {
 			var summary types.OpaqueNetworkSummary
diff --git a/simulator/performance_manager.go b/simulator/performance_manager.go
index a9bd5235..5f50510a 100644
--- a/simulator/performance_manager.go
+++ b/simulator/performance_manager.go
@@ -98,7 +98,7 @@ func (p *PerformanceManager) QueryPerfProviderSummary(ctx *Context, req *types.Q
 	body.Res = new(types.QueryPerfProviderSummaryResponse)
 
 	// The entity must exist
-	if ctx.Map.Get(req.Entity) == nil {
+	if Map.Get(req.Entity) == nil {
 		body.Fault_ = Fault("", &types.InvalidArgument{
 			InvalidProperty: "Entity",
 		})
diff --git a/simulator/portgroup.go b/simulator/portgroup.go
index a0973ae9..a59bd68f 100644
--- a/simulator/portgroup.go
+++ b/simulator/portgroup.go
@@ -60,11 +60,11 @@ func (s *DistributedVirtualPortgroup) ReconfigureDVPortgroupTask(ctx *Context, r
 
 func (s *DistributedVirtualPortgroup) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault {
 	task := CreateTask(s, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) {
-		vswitch := ctx.Map.Get(*s.Config.DistributedVirtualSwitch).(*DistributedVirtualSwitch)
-		ctx.Map.RemoveReference(ctx, vswitch, &vswitch.Portgroup, s.Reference())
-		ctx.Map.removeString(ctx, vswitch, &vswitch.Summary.PortgroupName, s.Name)
+		vswitch := Map.Get(*s.Config.DistributedVirtualSwitch).(*DistributedVirtualSwitch)
+		Map.RemoveReference(ctx, vswitch, &vswitch.Portgroup, s.Reference())
+		Map.removeString(ctx, vswitch, &vswitch.Summary.PortgroupName, s.Name)
 
-		f := ctx.Map.getEntityParent(vswitch, "Folder").(*Folder)
+		f := Map.getEntityParent(vswitch, "Folder").(*Folder)
 		folderRemoveChild(ctx, &f.Folder, s.Reference())
 
 		return nil, nil
diff --git a/simulator/property_collector.go b/simulator/property_collector.go
index ab80005c..c7c63672 100644
--- a/simulator/property_collector.go
+++ b/simulator/property_collector.go
@@ -58,7 +58,7 @@ func getObject(ctx *Context, ref types.ManagedObjectReference) (reflect.Value, b
 	if ctx.Session == nil {
 		// Even without permissions to access an object or specific fields, RetrieveProperties
 		// returns an ObjectContent response as long as the object exists.  See retrieveResult.add()
-		obj = ctx.Map.Get(ref)
+		obj = Map.Get(ref)
 	} else {
 		obj = ctx.Session.Get(ref)
 	}
@@ -331,7 +331,7 @@ func (rr *retrieveResult) collect(ctx *Context, ref types.ManagedObjectReference
 
 	rval, ok := getObject(ctx, ref)
 	if !ok {
-		// Possible if a test uses ctx.Map.Remove instead of Destroy_Task
+		// Possible if a test uses Map.Remove instead of Destroy_Task
 		tracef("object %s no longer exists", ref)
 		return
 	}
diff --git a/simulator/registry.go b/simulator/registry.go
index c91af59e..654ef24a 100644
--- a/simulator/registry.go
+++ b/simulator/registry.go
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2017-2021 VMware, Inc. All Rights Reserved.
+Copyright (c) 2017-2018 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -51,10 +51,6 @@ var refValueMap = map[string]string{
 }
 
 // Map is the default Registry instance.
-//
-// TODO/WIP: To support the eventual removal of this unsyncronized global
-// variable, the Map should be accessed through any Context.Map that is passed
-// in to functions that may need it.
 var Map = NewRegistry()
 
 // RegisterObject interface supports callbacks when objects are created, updated and deleted from the Registry
@@ -359,7 +355,7 @@ func (r *Registry) getEntityDatacenter(item mo.Entity) *Datacenter {
 }
 
 func (r *Registry) getEntityFolder(item mo.Entity, kind string) *mo.Folder {
-	dc := r.getEntityDatacenter(item)
+	dc := Map.getEntityDatacenter(item)
 
 	var ref types.ManagedObjectReference
 
@@ -373,7 +369,7 @@ func (r *Registry) getEntityFolder(item mo.Entity, kind string) *mo.Folder {
 	// 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, _ = asFolderMO(r.Get(child))
+			folder, _ = asFolderMO(Map.Get(child))
 			break
 		}
 	}
@@ -567,11 +563,6 @@ func (r *Registry) CustomFieldsManager() *CustomFieldsManager {
 	return r.Get(r.content().CustomFieldsManager.Reference()).(*CustomFieldsManager)
 }
 
-// TenantManager returns TenantManager singleton
-func (r *Registry) TenantManager() *TenantManager {
-	return r.Get(r.content().TenantManager.Reference()).(*TenantManager)
-}
-
 func (r *Registry) MarshalJSON() ([]byte, error) {
 	r.m.Lock()
 	defer r.m.Unlock()
diff --git a/simulator/resource_pool.go b/simulator/resource_pool.go
index a5f5a9aa..7df6e577 100644
--- a/simulator/resource_pool.go
+++ b/simulator/resource_pool.go
@@ -224,7 +224,7 @@ func (p *ResourcePool) ImportVApp(ctx *Context, req *types.ImportVApp) soap.HasF
 		Host:   req.Host,
 	})
 
-	ctask := ctx.Map.Get(res.(*methods.CreateVM_TaskBody).Res.Returnval).(*Task)
+	ctask := Map.Get(res.(*methods.CreateVM_TaskBody).Res.Returnval).(*Task)
 	ctask.Wait()
 
 	if ctask.Info.Error != nil {
@@ -347,7 +347,7 @@ func (p *ResourcePool) CreateVApp(req *types.CreateVApp) soap.HasFault {
 func (a *VirtualApp) CreateChildVMTask(ctx *Context, req *types.CreateChildVM_Task) soap.HasFault {
 	body := &methods.CreateChildVM_TaskBody{}
 
-	folder := ctx.Map.Get(*a.ParentFolder).(*Folder)
+	folder := Map.Get(*a.ParentFolder).(*Folder)
 
 	res := folder.CreateVMTask(ctx, &types.CreateVM_Task{
 		This:   folder.Self,
@@ -405,7 +405,7 @@ func (a *VirtualApp) CloneVAppTask(ctx *Context, req *types.CloneVApp_Task) soap
 				},
 			})
 
-			ctask := ctx.Map.Get(res.(*methods.CloneVM_TaskBody).Res.Returnval).(*Task)
+			ctask := Map.Get(res.(*methods.CloneVM_TaskBody).Res.Returnval).(*Task)
 			ctask.Wait()
 			if ctask.Info.Error != nil {
 				return nil, ctask.Info.Error.Fault
diff --git a/simulator/service_instance.go b/simulator/service_instance.go
index 3524d154..73238920 100644
--- a/simulator/service_instance.go
+++ b/simulator/service_instance.go
@@ -34,10 +34,7 @@ type ServiceInstance struct {
 }
 
 func NewServiceInstance(ctx *Context, content types.ServiceContent, folder mo.Folder) *ServiceInstance {
-	// TODO: This function ignores the passed in Map and operates on the
-	// global Map.
 	Map = NewRegistry()
-	ctx.Map = Map
 
 	s := &ServiceInstance{}
 
diff --git a/simulator/session_manager.go b/simulator/session_manager.go
index 8a0b5867..90b1e7a1 100644
--- a/simulator/session_manager.go
+++ b/simulator/session_manager.go
@@ -181,7 +181,7 @@ func (s *SessionManager) LoginByToken(ctx *Context, req *types.LoginByToken) soa
 func (s *SessionManager) Logout(ctx *Context, _ *types.Logout) soap.HasFault {
 	session := ctx.Session
 	s.delSession(session.Key)
-	pc := ctx.Map.content().PropertyCollector
+	pc := Map.content().PropertyCollector
 
 	for ref, obj := range ctx.Session.Registry.objects {
 		if ref == pc {
@@ -379,12 +379,12 @@ func (c *Context) WithLock(obj mo.Reference, f func()) {
 	// argument to accomplish this.
 	// Basic mutex locking will work even if obj doesn't belong to Map, but
 	// if obj implements sync.Locker, that custom locking will not be used.
-	c.Map.WithLock(c, obj, f)
+	Map.WithLock(c, obj, f)
 }
 
 // postEvent wraps EventManager.PostEvent for internal use, with a lock on the EventManager.
 func (c *Context) postEvent(events ...types.BaseEvent) {
-	m := c.Map.EventManager()
+	m := Map.EventManager()
 	c.WithLock(m, func() {
 		for _, event := range events {
 			m.PostEvent(c, &types.PostEvent{EventToPost: event})
diff --git a/simulator/snapshot.go b/simulator/snapshot.go
index 116fb326..ff72839d 100644
--- a/simulator/snapshot.go
+++ b/simulator/snapshot.go
@@ -74,7 +74,7 @@ func (v *VirtualMachineSnapshot) createSnapshotFiles() types.BaseMethodFault {
 func (v *VirtualMachineSnapshot) removeSnapshotFiles(ctx *Context) types.BaseMethodFault {
 	// TODO: also remove delta disks that were created when snapshot was taken
 
-	vm := ctx.Map.Get(v.Vm).(*VirtualMachine)
+	vm := Map.Get(v.Vm).(*VirtualMachine)
 
 	for idx, sLayout := range vm.Layout.Snapshot {
 		if sLayout.Key == v.Self {
@@ -92,8 +92,8 @@ func (v *VirtualMachineSnapshot) removeSnapshotFiles(ctx *Context) types.BaseMet
 						return fault
 					}
 
-					host := ctx.Map.Get(*vm.Runtime.Host).(*HostSystem)
-					datastore := ctx.Map.FindByName(p.Datastore, host.Datastore).(*Datastore)
+					host := Map.Get(*vm.Runtime.Host).(*HostSystem)
+					datastore := Map.FindByName(p.Datastore, host.Datastore).(*Datastore)
 					dFilePath := path.Join(datastore.Info.GetDatastoreInfo().Url, p.Path)
 
 					_ = os.Remove(dFilePath)
@@ -113,7 +113,7 @@ func (v *VirtualMachineSnapshot) RemoveSnapshotTask(ctx *Context, req *types.Rem
 	task := CreateTask(v, "removeSnapshot", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		var changes []types.PropertyChange
 
-		vm := ctx.Map.Get(v.Vm).(*VirtualMachine)
+		vm := Map.Get(v.Vm).(*VirtualMachine)
 		ctx.WithLock(vm, func() {
 			if vm.Snapshot.CurrentSnapshot != nil && *vm.Snapshot.CurrentSnapshot == req.This {
 				parent := findParentSnapshotInTree(vm.Snapshot.RootSnapshotList, req.This)
@@ -129,12 +129,12 @@ func (v *VirtualMachineSnapshot) RemoveSnapshotTask(ctx *Context, req *types.Rem
 				}
 			}
 
-			ctx.Map.Get(req.This).(*VirtualMachineSnapshot).removeSnapshotFiles(ctx)
+			Map.Get(req.This).(*VirtualMachineSnapshot).removeSnapshotFiles(ctx)
 
-			ctx.Map.Update(vm, changes)
+			Map.Update(vm, changes)
 		})
 
-		ctx.Map.Remove(ctx, req.This)
+		Map.Remove(ctx, req.This)
 
 		return nil, nil
 	})
@@ -148,10 +148,10 @@ func (v *VirtualMachineSnapshot) RemoveSnapshotTask(ctx *Context, req *types.Rem
 
 func (v *VirtualMachineSnapshot) RevertToSnapshotTask(ctx *Context, req *types.RevertToSnapshot_Task) soap.HasFault {
 	task := CreateTask(v.Vm, "revertToSnapshot", func(t *Task) (types.AnyType, types.BaseMethodFault) {
-		vm := ctx.Map.Get(v.Vm).(*VirtualMachine)
+		vm := Map.Get(v.Vm).(*VirtualMachine)
 
 		ctx.WithLock(vm, func() {
-			ctx.Map.Update(vm, []types.PropertyChange{
+			Map.Update(vm, []types.PropertyChange{
 				{Name: "snapshot.currentSnapshot", Val: v.Self},
 			})
 		})
diff --git a/simulator/task.go b/simulator/task.go
index bd43f742..7a465b1d 100644
--- a/simulator/task.go
+++ b/simulator/task.go
@@ -95,11 +95,7 @@ func (tr *taskReference) Reference() types.ManagedObjectReference {
 
 func (t *Task) Run(ctx *Context) types.ManagedObjectReference {
 	t.ctx = ctx
-	// alias the global Map to reduce data races in tests that reset the
-	// global Map variable.
-	vimMap := Map
-
-	vimMap.AtomicUpdate(t.ctx, t, []types.PropertyChange{
+	Map.AtomicUpdate(t.ctx, t, []types.PropertyChange{
 		{Name: "info.startTime", Val: time.Now()},
 		{Name: "info.state", Val: types.TaskInfoStateRunning},
 	})
@@ -107,10 +103,9 @@ func (t *Task) Run(ctx *Context) types.ManagedObjectReference {
 	tr := &taskReference{
 		Self: *t.Info.Entity,
 	}
-
 	// in most cases, the caller already holds this lock, and we would like
 	// the lock to be held across the "hand off" to the async goroutine.
-	unlock := vimMap.AcquireLock(ctx, tr)
+	unlock := Map.AcquireLock(ctx, tr)
 
 	go func() {
 		TaskDelay.delay(t.Info.Name)
@@ -127,7 +122,7 @@ func (t *Task) Run(ctx *Context) types.ManagedObjectReference {
 			}
 		}
 
-		vimMap.AtomicUpdate(t.ctx, t, []types.PropertyChange{
+		Map.AtomicUpdate(t.ctx, t, []types.PropertyChange{
 			{Name: "info.completeTime", Val: time.Now()},
 			{Name: "info.state", Val: state},
 			{Name: "info.result", Val: res},
diff --git a/simulator/tenant_manager.go b/simulator/tenant_manager.go
deleted file mode 100644
index 51f06d61..00000000
--- a/simulator/tenant_manager.go
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
-Copyright (c) 2021 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package simulator
-
-import (
-	"github.com/vmware/govmomi/vim25/methods"
-	"github.com/vmware/govmomi/vim25/mo"
-	"github.com/vmware/govmomi/vim25/soap"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-type TenantManager struct {
-	mo.TenantTenantManager
-
-	spEntities map[types.ManagedObjectReference]bool
-}
-
-func (t *TenantManager) init(r *Registry) {
-	t.spEntities = make(map[types.ManagedObjectReference]bool)
-}
-
-func (t *TenantManager) markEntities(entities []types.ManagedObjectReference) {
-	for _, e := range entities {
-		t.spEntities[e] = true
-	}
-}
-
-func (t *TenantManager) unmarkEntities(entities []types.ManagedObjectReference) {
-	for _, e := range entities {
-		_, ok := t.spEntities[e]
-		if ok {
-			delete(t.spEntities, e)
-		}
-	}
-}
-
-func (t *TenantManager) getEntities() []types.ManagedObjectReference {
-	entities := []types.ManagedObjectReference{}
-	for e := range t.spEntities {
-		entities = append(entities, e)
-	}
-	return entities
-}
-
-func (t *TenantManager) MarkServiceProviderEntities(req *types.MarkServiceProviderEntities) soap.HasFault {
-	body := new(methods.MarkServiceProviderEntitiesBody)
-	t.markEntities(req.Entity)
-	body.Res = &types.MarkServiceProviderEntitiesResponse{}
-	return body
-}
-
-func (t *TenantManager) UnmarkServiceProviderEntities(req *types.UnmarkServiceProviderEntities) soap.HasFault {
-	body := new(methods.UnmarkServiceProviderEntitiesBody)
-	t.unmarkEntities(req.Entity)
-	body.Res = &types.UnmarkServiceProviderEntitiesResponse{}
-	return body
-}
-
-func (t *TenantManager) RetrieveServiceProviderEntities(req *types.RetrieveServiceProviderEntities) soap.HasFault {
-	body := new(methods.RetrieveServiceProviderEntitiesBody)
-	body.Res = &types.RetrieveServiceProviderEntitiesResponse{
-		Returnval: t.getEntities(),
-	}
-	return body
-}
diff --git a/simulator/view_manager.go b/simulator/view_manager.go
index 62d91bcc..62653953 100644
--- a/simulator/view_manager.go
+++ b/simulator/view_manager.go
@@ -69,7 +69,7 @@ func destroyView(ref types.ManagedObjectReference) soap.HasFault {
 func (m *ViewManager) CreateContainerView(ctx *Context, req *types.CreateContainerView) soap.HasFault {
 	body := &methods.CreateContainerViewBody{}
 
-	root := ctx.Map.Get(req.Container)
+	root := Map.Get(req.Container)
 	if root == nil {
 		body.Fault_ = Fault("", &types.ManagedObjectNotFound{Obj: req.Container})
 		return body
@@ -216,7 +216,7 @@ func (v *ContainerView) PutObject(obj mo.Reference) {
 }
 
 func (v *ContainerView) RemoveObject(ctx *Context, obj types.ManagedObjectReference) {
-	ctx.Map.RemoveReference(ctx, v, &v.View, obj)
+	Map.RemoveReference(ctx, v, &v.View, obj)
 }
 
 func (*ContainerView) UpdateObject(mo.Reference, []types.PropertyChange) {}
diff --git a/simulator/virtual_disk_manager.go b/simulator/virtual_disk_manager.go
index 88844b23..ee27ec14 100644
--- a/simulator/virtual_disk_manager.go
+++ b/simulator/virtual_disk_manager.go
@@ -124,7 +124,7 @@ func (m *VirtualDiskManager) DeleteVirtualDiskTask(ctx *Context, req *types.Dele
 
 func (m *VirtualDiskManager) MoveVirtualDiskTask(ctx *Context, req *types.MoveVirtualDisk_Task) soap.HasFault {
 	task := CreateTask(m, "moveVirtualDisk", func(*Task) (types.AnyType, types.BaseMethodFault) {
-		fm := ctx.Map.FileManager()
+		fm := Map.FileManager()
 
 		dest := vdmNames(req.DestName)
 
@@ -155,12 +155,12 @@ func (m *VirtualDiskManager) MoveVirtualDiskTask(ctx *Context, req *types.MoveVi
 func (m *VirtualDiskManager) CopyVirtualDiskTask(ctx *Context, req *types.CopyVirtualDisk_Task) soap.HasFault {
 	task := CreateTask(m, "copyVirtualDisk", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		if req.DestSpec != nil {
-			if ctx.Map.IsVPX() {
+			if Map.IsVPX() {
 				return nil, new(types.NotImplemented)
 			}
 		}
 
-		fm := ctx.Map.FileManager()
+		fm := Map.FileManager()
 
 		dest := vdmNames(req.DestName)
 
@@ -195,10 +195,10 @@ func virtualDiskUUID(dc *types.ManagedObjectReference, file string) string {
 	return uuid.NewSHA1(uuid.NameSpaceOID, []byte(file)).String()
 }
 
-func (m *VirtualDiskManager) QueryVirtualDiskUuid(ctx *Context, req *types.QueryVirtualDiskUuid) soap.HasFault {
+func (m *VirtualDiskManager) QueryVirtualDiskUuid(_ *Context, req *types.QueryVirtualDiskUuid) soap.HasFault {
 	body := new(methods.QueryVirtualDiskUuidBody)
 
-	fm := ctx.Map.FileManager()
+	fm := Map.FileManager()
 
 	file, fault := fm.resolve(req.Datacenter, req.Name)
 	if fault != nil {
diff --git a/simulator/virtual_machine.go b/simulator/virtual_machine.go
index 032dc11f..331abf34 100644
--- a/simulator/virtual_machine.go
+++ b/simulator/virtual_machine.go
@@ -59,9 +59,9 @@ func asVirtualMachineMO(obj mo.Reference) (*mo.VirtualMachine, bool) {
 func NewVirtualMachine(ctx *Context, parent types.ManagedObjectReference, spec *types.VirtualMachineConfigSpec) (*VirtualMachine, types.BaseMethodFault) {
 	vm := &VirtualMachine{}
 	vm.Parent = &parent
-	ctx.Map.reference(vm)
+	Map.reference(vm)
 
-	folder := ctx.Map.Get(parent)
+	folder := Map.Get(parent)
 
 	if spec.Name == "" {
 		return vm, &types.InvalidVmConfig{Property: "configSpec.name"}
@@ -102,8 +102,8 @@ func NewVirtualMachine(ctx *Context, parent types.ManagedObjectReference, spec *
 		vmx.Path = spec.Name
 	}
 
-	dc := ctx.Map.getEntityDatacenter(folder.(mo.Entity))
-	ds := ctx.Map.FindByName(vmx.Datastore, dc.Datastore).(*Datastore)
+	dc := Map.getEntityDatacenter(folder.(mo.Entity))
+	ds := Map.FindByName(vmx.Datastore, dc.Datastore).(*Datastore)
 	dir := path.Join(ds.Info.GetDatastoreInfo().Url, vmx.Path)
 
 	if path.Ext(vmx.Path) == ".vmx" {
@@ -333,6 +333,55 @@ func (vm *VirtualMachine) apply(spec *types.VirtualMachineConfigSpec) {
 	vm.Config.Modified = time.Now()
 }
 
+// updateVAppProperty updates the simulator VM with the specified VApp properties.
+func (vm *VirtualMachine) updateVAppProperty(spec *types.VmConfigSpec) types.BaseMethodFault {
+	ps := make([]types.VAppPropertyInfo, 0)
+
+	if vm.Config.VAppConfig != nil && vm.Config.VAppConfig.GetVmConfigInfo() != nil {
+		ps = vm.Config.VAppConfig.GetVmConfigInfo().Property
+	}
+
+	for _, prop := range spec.Property {
+		var foundIndex int
+		exists := false
+		// Check if the specified property exists or not. This helps rejecting invalid
+		// operations (e.g., Adding a VApp property that already exists)
+		for i, p := range ps {
+			if p.Key == prop.Info.Key {
+				exists = true
+				foundIndex = i
+				break
+			}
+		}
+
+		switch prop.Operation {
+		case types.ArrayUpdateOperationAdd:
+			if exists {
+				return new(types.InvalidArgument)
+			}
+			ps = append(ps, *prop.Info)
+		case types.ArrayUpdateOperationEdit:
+			if !exists {
+				return new(types.InvalidArgument)
+			}
+			ps[foundIndex] = *prop.Info
+		case types.ArrayUpdateOperationRemove:
+			if !exists {
+				return new(types.InvalidArgument)
+			}
+			ps = append(ps[:foundIndex], ps[foundIndex+1:]...)
+		}
+	}
+
+	if vm.Config.VAppConfig == nil {
+		vm.Config.VAppConfig = &types.VmConfigInfo{}
+	}
+
+	vm.Config.VAppConfig.GetVmConfigInfo().Property = ps
+
+	return nil
+}
+
 var extraConfigAlias = map[string]string{
 	"ip0": "SET.guest.ipAddress",
 }
@@ -413,6 +462,12 @@ func (vm *VirtualMachine) configure(ctx *Context, spec *types.VirtualMachineConf
 		}
 	}
 
+	if spec.VAppConfig != nil {
+		if err := vm.updateVAppProperty(spec.VAppConfig.GetVmConfigSpec()); err != nil {
+			return err
+		}
+	}
+
 	return vm.configureDevices(ctx, spec)
 }
 
@@ -1071,7 +1126,7 @@ func (vm *VirtualMachine) configureDevice(ctx *Context, devices object.VirtualDe
 
 	label := devices.Name(device)
 	summary := label
-	dc := ctx.Map.getEntityDatacenter(ctx.Map.Get(*vm.Parent).(mo.Entity))
+	dc := Map.getEntityDatacenter(Map.Get(*vm.Parent).(mo.Entity))
 
 	switch x := device.(type) {
 	case types.BaseVirtualEthernetCard:
@@ -1083,7 +1138,7 @@ func (vm *VirtualMachine) configureDevice(ctx *Context, devices object.VirtualDe
 		case *types.VirtualEthernetCardNetworkBackingInfo:
 			name = b.DeviceName
 			summary = name
-			net = ctx.Map.FindByName(b.DeviceName, dc.Network).Reference()
+			net = Map.FindByName(b.DeviceName, dc.Network).Reference()
 			b.Network = &net
 		case *types.VirtualEthernetCardDistributedVirtualPortBackingInfo:
 			summary = fmt.Sprintf("DVSwitch: %s", b.Port.SwitchUuid)
@@ -1094,7 +1149,7 @@ func (vm *VirtualMachine) configureDevice(ctx *Context, devices object.VirtualDe
 			}
 		}
 
-		ctx.Map.Update(vm, []types.PropertyChange{
+		Map.Update(vm, []types.PropertyChange{
 			{Name: "summary.config.numEthernetCards", Val: vm.Summary.Config.NumEthernetCards + 1},
 			{Name: "network", Val: append(vm.Network, net)},
 		})
@@ -1152,7 +1207,7 @@ func (vm *VirtualMachine) configureDevice(ctx *Context, devices object.VirtualDe
 				return err
 			}
 
-			ctx.Map.Update(vm, []types.PropertyChange{
+			Map.Update(vm, []types.PropertyChange{
 				{Name: "summary.config.numVirtualDisks", Val: vm.Summary.Config.NumVirtualDisks + 1},
 			})
 
@@ -1260,8 +1315,8 @@ func (vm *VirtualMachine) removeDevice(ctx *Context, devices object.VirtualDevic
 				}
 
 				if file != "" {
-					dc := ctx.Map.getEntityDatacenter(vm)
-					dm := ctx.Map.VirtualDiskManager()
+					dc := Map.getEntityDatacenter(vm)
+					dm := Map.VirtualDiskManager()
 					if dc == nil {
 						continue // parent was destroyed
 					}
@@ -1269,11 +1324,11 @@ func (vm *VirtualMachine) removeDevice(ctx *Context, devices object.VirtualDevic
 						Name:       file,
 						Datacenter: &dc.Self,
 					})
-					ctask := ctx.Map.Get(res.(*methods.DeleteVirtualDisk_TaskBody).Res.Returnval).(*Task)
+					ctask := Map.Get(res.(*methods.DeleteVirtualDisk_TaskBody).Res.Returnval).(*Task)
 					ctask.Wait()
 				}
 			}
-			ctx.Map.Update(vm, []types.PropertyChange{
+			Map.Update(vm, []types.PropertyChange{
 				{Name: "summary.config.numVirtualDisks", Val: vm.Summary.Config.NumVirtualDisks - 1},
 			})
 
@@ -1291,7 +1346,7 @@ func (vm *VirtualMachine) removeDevice(ctx *Context, devices object.VirtualDevic
 
 			networks := vm.Network
 			RemoveReference(&networks, net)
-			ctx.Map.Update(vm, []types.PropertyChange{
+			Map.Update(vm, []types.PropertyChange{
 				{Name: "summary.config.numEthernetCards", Val: vm.Summary.Config.NumEthernetCards - 1},
 				{Name: "network", Val: networks},
 			})
@@ -1416,7 +1471,7 @@ func (vm *VirtualMachine) configureDevices(ctx *Context, spec *types.VirtualMach
 		}
 	}
 
-	ctx.Map.Update(vm, []types.PropertyChange{
+	Map.Update(vm, []types.PropertyChange{
 		{Name: "config.hardware.device", Val: []types.BaseVirtualDevice(devices)},
 	})
 
@@ -1484,7 +1539,7 @@ func (c *powerVMTask) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 		)
 	}
 
-	c.ctx.Map.Update(c.VirtualMachine, []types.PropertyChange{
+	Map.Update(c.VirtualMachine, []types.PropertyChange{
 		{Name: "runtime.powerState", Val: c.state},
 		{Name: "summary.runtime.powerState", Val: c.state},
 		{Name: "summary.runtime.bootTime", Val: boot},
@@ -1535,14 +1590,14 @@ func (vm *VirtualMachine) SuspendVMTask(ctx *Context, req *types.SuspendVM_Task)
 func (vm *VirtualMachine) ResetVMTask(ctx *Context, req *types.ResetVM_Task) soap.HasFault {
 	task := CreateTask(vm, "reset", func(task *Task) (types.AnyType, types.BaseMethodFault) {
 		res := vm.PowerOffVMTask(ctx, &types.PowerOffVM_Task{This: vm.Self})
-		ctask := ctx.Map.Get(res.(*methods.PowerOffVM_TaskBody).Res.Returnval).(*Task)
+		ctask := Map.Get(res.(*methods.PowerOffVM_TaskBody).Res.Returnval).(*Task)
 		ctask.Wait()
 		if ctask.Info.Error != nil {
 			return nil, ctask.Info.Error.Fault
 		}
 
 		res = vm.PowerOnVMTask(ctx, &types.PowerOnVM_Task{This: vm.Self})
-		ctask = ctx.Map.Get(res.(*methods.PowerOnVM_TaskBody).Res.Returnval).(*Task)
+		ctask = Map.Get(res.(*methods.PowerOnVM_TaskBody).Res.Returnval).(*Task)
 		ctask.Wait()
 
 		return nil, nil
@@ -1611,7 +1666,7 @@ func (vm *VirtualMachine) UpgradeVMTask(ctx *Context, req *types.UpgradeVM_Task)
 
 	task := CreateTask(vm, "upgradeVm", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		if vm.Config.Version != esx.HardwareVersion {
-			ctx.Map.Update(vm, []types.PropertyChange{{
+			Map.Update(vm, []types.PropertyChange{{
 				Name: "config.version", Val: esx.HardwareVersion,
 			}})
 		}
@@ -1647,7 +1702,7 @@ func (vm *VirtualMachine) DestroyTask(ctx *Context, req *types.Destroy_Task) soa
 		vm.configureDevices(ctx, &types.VirtualMachineConfigSpec{DeviceChange: spec})
 
 		// Delete VM files from the datastore (ignoring result for now)
-		m := ctx.Map.FileManager()
+		m := Map.FileManager()
 
 		_ = m.DeleteDatastoreFileTask(ctx, &types.DeleteDatastoreFile_Task{
 			This:       m.Reference(),
@@ -1683,25 +1738,25 @@ func (vm *VirtualMachine) UnregisterVM(ctx *Context, c *types.UnregisterVM) soap
 		return r
 	}
 
-	host := ctx.Map.Get(*vm.Runtime.Host).(*HostSystem)
-	ctx.Map.RemoveReference(ctx, host, &host.Vm, vm.Self)
+	host := Map.Get(*vm.Runtime.Host).(*HostSystem)
+	Map.RemoveReference(ctx, host, &host.Vm, vm.Self)
 
 	if vm.ResourcePool != nil {
-		switch pool := ctx.Map.Get(*vm.ResourcePool).(type) {
+		switch pool := Map.Get(*vm.ResourcePool).(type) {
 		case *ResourcePool:
-			ctx.Map.RemoveReference(ctx, pool, &pool.Vm, vm.Self)
+			Map.RemoveReference(ctx, pool, &pool.Vm, vm.Self)
 		case *VirtualApp:
-			ctx.Map.RemoveReference(ctx, pool, &pool.Vm, vm.Self)
+			Map.RemoveReference(ctx, pool, &pool.Vm, vm.Self)
 		}
 	}
 
 	for i := range vm.Datastore {
-		ds := ctx.Map.Get(vm.Datastore[i]).(*Datastore)
-		ctx.Map.RemoveReference(ctx, ds, &ds.Vm, vm.Self)
+		ds := Map.Get(vm.Datastore[i]).(*Datastore)
+		Map.RemoveReference(ctx, ds, &ds.Vm, vm.Self)
 	}
 
 	ctx.postEvent(&types.VmRemovedEvent{VmEvent: vm.event()})
-	if f, ok := asFolderMO(ctx.Map.getEntityParent(vm, "Folder")); ok {
+	if f, ok := asFolderMO(Map.getEntityParent(vm, "Folder")); ok {
 		folderRemoveChild(ctx, f, c.This)
 	}
 
@@ -1728,8 +1783,8 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
 		destHost = req.Spec.Location.Host
 	}
 
-	folder, _ := asFolderMO(ctx.Map.Get(req.Folder))
-	host := ctx.Map.Get(*destHost).(*HostSystem)
+	folder, _ := asFolderMO(Map.Get(req.Folder))
+	host := Map.Get(*destHost).(*HostSystem)
 	event := vm.event()
 
 	ctx.postEvent(&types.VmBeingClonedEvent{
@@ -1744,7 +1799,7 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
 	vmx := vm.vmx(nil)
 	vmx.Path = req.Name
 	if ref := req.Spec.Location.Datastore; ref != nil {
-		ds := ctx.Map.Get(*ref).(*Datastore).Name
+		ds := Map.Get(*ref).(*Datastore).Name
 		vmx.Datastore = ds
 	}
 
@@ -1798,21 +1853,21 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
 			})
 		}
 
-		res := ctx.Map.Get(req.Folder).(vmFolder).CreateVMTask(ctx, &types.CreateVM_Task{
+		res := Map.Get(req.Folder).(vmFolder).CreateVMTask(ctx, &types.CreateVM_Task{
 			This:   folder.Self,
 			Config: config,
 			Pool:   *pool,
 			Host:   destHost,
 		})
 
-		ctask := ctx.Map.Get(res.(*methods.CreateVM_TaskBody).Res.Returnval).(*Task)
+		ctask := Map.Get(res.(*methods.CreateVM_TaskBody).Res.Returnval).(*Task)
 		ctask.Wait()
 		if ctask.Info.Error != nil {
 			return nil, ctask.Info.Error.Fault
 		}
 
 		ref := ctask.Info.Result.(types.ManagedObjectReference)
-		clone := ctx.Map.Get(ref).(*VirtualMachine)
+		clone := Map.Get(ref).(*VirtualMachine)
 		clone.configureDevices(ctx, &types.VirtualMachineConfigSpec{DeviceChange: req.Spec.Location.DeviceChange})
 		if req.Spec.Config != nil && req.Spec.Config.DeviceChange != nil {
 			clone.configureDevices(ctx, &types.VirtualMachineConfigSpec{DeviceChange: req.Spec.Config.DeviceChange})
@@ -1842,8 +1897,8 @@ func (vm *VirtualMachine) RelocateVMTask(ctx *Context, req *types.RelocateVM_Tas
 		var changes []types.PropertyChange
 
 		if ref := req.Spec.Datastore; ref != nil {
-			ds := ctx.Map.Get(*ref).(*Datastore)
-			ctx.Map.RemoveReference(ctx, ds, &ds.Vm, *ref)
+			ds := Map.Get(*ref).(*Datastore)
+			Map.RemoveReference(ctx, ds, &ds.Vm, *ref)
 
 			// TODO: migrate vm.Config.Files, vm.Summary.Config.VmPathName, vm.Layout and vm.LayoutEx
 
@@ -1851,15 +1906,15 @@ func (vm *VirtualMachine) RelocateVMTask(ctx *Context, req *types.RelocateVM_Tas
 		}
 
 		if ref := req.Spec.Pool; ref != nil {
-			pool := ctx.Map.Get(*ref).(*ResourcePool)
-			ctx.Map.RemoveReference(ctx, pool, &pool.Vm, *ref)
+			pool := Map.Get(*ref).(*ResourcePool)
+			Map.RemoveReference(ctx, pool, &pool.Vm, *ref)
 
 			changes = append(changes, types.PropertyChange{Name: "resourcePool", Val: ref})
 		}
 
 		if ref := req.Spec.Host; ref != nil {
-			host := ctx.Map.Get(*ref).(*HostSystem)
-			ctx.Map.RemoveReference(ctx, host, &host.Vm, *ref)
+			host := Map.Get(*ref).(*HostSystem)
+			Map.RemoveReference(ctx, host, &host.Vm, *ref)
 
 			changes = append(changes,
 				types.PropertyChange{Name: "runtime.host", Val: ref},
@@ -1868,7 +1923,7 @@ func (vm *VirtualMachine) RelocateVMTask(ctx *Context, req *types.RelocateVM_Tas
 		}
 
 		if ref := req.Spec.Folder; ref != nil {
-			folder := ctx.Map.Get(*ref).(*Folder)
+			folder := Map.Get(*ref).(*Folder)
 			folder.MoveIntoFolderTask(ctx, &types.MoveIntoFolder_Task{
 				List: []types.ManagedObjectReference{vm.Self},
 			})
@@ -1881,7 +1936,7 @@ func (vm *VirtualMachine) RelocateVMTask(ctx *Context, req *types.RelocateVM_Tas
 			SourceDatastore:  ctx.Map.Get(vm.Datastore[0]).(*Datastore).eventArgument(),
 		})
 
-		ctx.Map.Update(vm, changes)
+		Map.Update(vm, changes)
 
 		return nil, nil
 	})
@@ -1970,7 +2025,7 @@ func (vm *VirtualMachine) customize(ctx *Context) {
 	}
 
 	vm.imc = nil
-	ctx.Map.Update(vm, changes)
+	Map.Update(vm, changes)
 	ctx.postEvent(&types.CustomizationSucceeded{CustomizationEvent: event})
 }
 
@@ -2021,7 +2076,7 @@ func (vm *VirtualMachine) CreateSnapshotTask(ctx *Context, req *types.CreateSnap
 		snapshot.Vm = vm.Reference()
 		snapshot.Config = *vm.Config
 
-		ctx.Map.Put(snapshot)
+		Map.Put(snapshot)
 
 		treeItem := types.VirtualMachineSnapshotTree{
 			Snapshot:        snapshot.Self,
@@ -2038,7 +2093,7 @@ func (vm *VirtualMachine) CreateSnapshotTask(ctx *Context, req *types.CreateSnap
 
 		cur := vm.Snapshot.CurrentSnapshot
 		if cur != nil {
-			parent := ctx.Map.Get(*cur).(*VirtualMachineSnapshot)
+			parent := Map.Get(*cur).(*VirtualMachineSnapshot)
 			parent.ChildSnapshot = append(parent.ChildSnapshot, snapshot.Self)
 
 			ss := findSnapshotInTree(vm.Snapshot.RootSnapshotList, *cur)
@@ -2053,7 +2108,7 @@ func (vm *VirtualMachine) CreateSnapshotTask(ctx *Context, req *types.CreateSnap
 		snapshot.createSnapshotFiles()
 
 		changes = append(changes, types.PropertyChange{Name: "snapshot.currentSnapshot", Val: snapshot.Self})
-		ctx.Map.Update(vm, changes)
+		Map.Update(vm, changes)
 
 		return snapshot.Self, nil
 	})
@@ -2093,13 +2148,13 @@ func (vm *VirtualMachine) RemoveAllSnapshotsTask(ctx *Context, req *types.Remove
 
 		refs := allSnapshotsInTree(vm.Snapshot.RootSnapshotList)
 
-		ctx.Map.Update(vm, []types.PropertyChange{
+		Map.Update(vm, []types.PropertyChange{
 			{Name: "snapshot", Val: nil},
 		})
 
 		for _, ref := range refs {
-			ctx.Map.Get(ref).(*VirtualMachineSnapshot).removeSnapshotFiles(ctx)
-			ctx.Map.Remove(ctx, ref)
+			Map.Get(ref).(*VirtualMachineSnapshot).removeSnapshotFiles(ctx)
+			Map.Remove(ctx, ref)
 		}
 
 		return nil, nil
@@ -2134,7 +2189,7 @@ func (vm *VirtualMachine) ShutdownGuest(ctx *Context, c *types.ShutdownGuest) so
 	)
 	vm.run.stop(ctx, vm)
 
-	ctx.Map.Update(vm, []types.PropertyChange{
+	Map.Update(vm, []types.PropertyChange{
 		{Name: "runtime.powerState", Val: types.VirtualMachinePowerStatePoweredOff},
 		{Name: "summary.runtime.powerState", Val: types.VirtualMachinePowerStatePoweredOff},
 	})
diff --git a/simulator/vstorage_object_manager.go b/simulator/vstorage_object_manager.go
index 5dc138e0..68d0ecf4 100644
--- a/simulator/vstorage_object_manager.go
+++ b/simulator/vstorage_object_manager.go
@@ -288,9 +288,9 @@ func (m *VcenterVStorageObjectManager) DeleteVStorageObjectTask(ctx *Context, re
 		}
 
 		backing := obj.Config.Backing.(*types.BaseConfigInfoDiskFileBackingInfo)
-		ds := ctx.Map.Get(req.Datastore).(*Datastore)
-		dc := ctx.Map.getEntityDatacenter(ds)
-		dm := ctx.Map.VirtualDiskManager()
+		ds := Map.Get(req.Datastore).(*Datastore)
+		dc := Map.getEntityDatacenter(ds)
+		dm := Map.VirtualDiskManager()
 		dm.DeleteVirtualDiskTask(ctx, &types.DeleteVirtualDisk_Task{
 			Name:       backing.FilePath,
 			Datacenter: &dc.Self,


Test patch:
diff --git a/simulator/performance_manager_test.go b/simulator/performance_manager_test.go
index d74251cc..38e5f46d 100644
--- a/simulator/performance_manager_test.go
+++ b/simulator/performance_manager_test.go
@@ -276,10 +276,10 @@ func testPerfQuery(ctx context.Context, m *Model, e mo.Entity, interval int32) e
 	// Single metric, single VM
 	//
 	qs := []types.PerfQuerySpec{
-		{
+		types.PerfQuerySpec{
 			MaxSample:  4,
 			IntervalId: interval,
-			MetricId:   []types.PerfMetricId{{CounterId: 1, Instance: ""}},
+			MetricId:   []types.PerfMetricId{types.PerfMetricId{CounterId: 1, Instance: ""}},
 			Entity:     e.Reference(),
 		},
 	}
diff --git a/simulator/portgroup_test.go b/simulator/portgroup_test.go
index 73d27fee..5bb2ebc9 100644
--- a/simulator/portgroup_test.go
+++ b/simulator/portgroup_test.go
@@ -46,7 +46,7 @@ func TestReconfigurePortgroup(t *testing.T) {
 		Map.Any("DistributedVirtualSwitch").Reference())
 
 	spec := []types.DVPortgroupConfigSpec{
-		{
+		types.DVPortgroupConfigSpec{
 			Name:     "pg1",
 			NumPorts: 10,
 		},
diff --git a/simulator/tenant_manager_test.go b/simulator/tenant_manager_test.go
deleted file mode 100644
index 9e3dc8b1..00000000
--- a/simulator/tenant_manager_test.go
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
-Copyright (c) 2021 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package simulator
-
-import (
-	"context"
-	"reflect"
-	"sort"
-	"strings"
-	"testing"
-
-	"github.com/vmware/govmomi"
-	"github.com/vmware/govmomi/object"
-	"github.com/vmware/govmomi/vim25"
-	"github.com/vmware/govmomi/vim25/soap"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-func sortMoRefSlice(a []types.ManagedObjectReference) {
-	sort.SliceStable(a, func(i, j int) bool {
-		lhs, rhs := a[i], a[j]
-		switch strings.Compare(lhs.Type, rhs.Type) {
-		case -1:
-			return true
-		case 1:
-			return false
-		}
-		return lhs.Value < rhs.Value
-	})
-}
-
-func TestTenantManagerForOldClients(t *testing.T) {
-	// ServiceContent TenantManager field is not present in older (<6.9.1) vmodl
-	// (e.g. response to RetrieveServiceConent() API or propery collector on
-	// ServiceInstance object), this field should be set only if the client is newer.
-	// Currently TenantManager is not set in vpx simulator's ServiceContent, and
-	// would be added once simulator supports client vmodl versioning properly.
-	t.Skip("needs vmodl versioning")
-
-	ctx := context.Background()
-	m := VPX()
-	defer m.Remove()
-
-	err := m.Create()
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	s := m.Service.NewServer()
-	defer s.Close()
-
-	// Ensure non-nil moref being returned for ServiceContent.TenantManger for newer vim version clients.
-	newSoapClient := soap.NewClient(s.URL, true)
-	newSoapClient.Version = "6.9.1"
-	newVimClient, err := vim25.NewClient(ctx, newSoapClient)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if newVimClient.ServiceContent.TenantManager == nil {
-		t.Fatal("Expected retrieved ServiceContent.TenantManager to be non-nil")
-	}
-
-	// Ensure non-nil moref being returned for ServiceContent.TenantManger for default version used in vim25 client.
-	defaultSoapClient := soap.NewClient(s.URL, true)
-	// No version being set for soap client
-	defaultVimClient, err := vim25.NewClient(ctx, defaultSoapClient)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if defaultVimClient.ServiceContent.TenantManager == nil {
-		t.Fatal("Expected retrieved ServiceContent.TenantManager to be non-nil")
-	}
-
-	// Ensure nil being returned for ServiceContent.TenantManger for older vim version clients.
-	oldSoapClient := soap.NewClient(s.URL, true)
-	oldSoapClient.Version = "6.5"
-	oldVimClient, err := vim25.NewClient(ctx, oldSoapClient)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if oldVimClient.ServiceContent.TenantManager != nil {
-		t.Fatalf("Expected retrieved ServiceContent.TenantManager to be nil but found %v", oldVimClient.ServiceContent.TenantManager)
-	}
-
-}
-
-func TestTenantManagerVPX(t *testing.T) {
-	// ServiceContent TenantManager field is not present in older (<6.9.1) vmodl
-	// (e.g. response to RetrieveServiceConent() API or propery collector on
-	// ServiceInstance object), this field should be set only if the client is newer.
-	// Currently TenantManager is not set in vpx simulator's ServiceContent, and
-	// would be added once simulator supports client vmodl versioning properly.
-	t.Skip("needs vmodl versioning")
-
-	ctx := context.Background()
-	m := VPX()
-	defer m.Remove()
-
-	err := m.Create()
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	s := m.Service.NewServer()
-	defer s.Close()
-
-	c, err := govmomi.NewClient(ctx, s.URL, true)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	tenantManager := object.NewTenantManager(c.Client)
-	serviceProviderEntities := []types.ManagedObjectReference{
-		{Type: "VirtualMachine", Value: "vm-123"},
-		{Type: "HostSystem", Value: "host-1"},
-	}
-	sortMoRefSlice(serviceProviderEntities)
-
-	// "Read your writes", mark entities and verify they are marked.
-	err = tenantManager.MarkServiceProviderEntities(ctx, serviceProviderEntities)
-	if err != nil {
-		t.Fatal(err)
-	}
-	markedEntities, err := tenantManager.RetrieveServiceProviderEntities(ctx)
-	if err != nil {
-		t.Fatal(err)
-	}
-	sortMoRefSlice(markedEntities)
-	if !reflect.DeepEqual(serviceProviderEntities, markedEntities) {
-		t.Errorf("Requested-to-be-marked entities mismatch with acutally marked: %+v, %+v", serviceProviderEntities, markedEntities)
-	}
-
-	// Repeatedely mark same entities and verify they are deduped.
-	err = tenantManager.MarkServiceProviderEntities(ctx, serviceProviderEntities)
-	if err != nil {
-		t.Fatal(err)
-	}
-	markedEntities, err = tenantManager.RetrieveServiceProviderEntities(ctx)
-	if err != nil {
-		t.Fatal(err)
-	}
-	sortMoRefSlice(markedEntities)
-	if !reflect.DeepEqual(serviceProviderEntities, markedEntities) {
-		t.Errorf("Requested-to-be-marked entities mismatch with acutally marked: %+v, %+v", serviceProviderEntities, markedEntities)
-	}
-
-	// Unmark not-previously-marked entity and verify no-op.
-	unknownEntities := []types.ManagedObjectReference{{Type: "Folder", Value: "group-3"}}
-	err = tenantManager.UnmarkServiceProviderEntities(ctx, unknownEntities)
-	if err != nil {
-		t.Fatal(err)
-	}
-	markedEntities, err = tenantManager.RetrieveServiceProviderEntities(ctx)
-	if err != nil {
-		t.Fatal(err)
-	}
-	sortMoRefSlice(markedEntities)
-	if !reflect.DeepEqual(serviceProviderEntities, markedEntities) {
-		t.Errorf("Requested-to-be-marked entities mismatch with acutally marked: %+v, %+v", serviceProviderEntities, markedEntities)
-	}
-
-	// Unmark marked entities and verify no longer marked.
-	err = tenantManager.UnmarkServiceProviderEntities(ctx, serviceProviderEntities)
-	if err != nil {
-		t.Fatal(err)
-	}
-	markedEntities, err = tenantManager.RetrieveServiceProviderEntities(ctx)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if len(markedEntities) > 0 {
-		t.Errorf("Expected all entities to be unmarked but still found marked: %+v", markedEntities)
-	}
-}
diff --git a/simulator/virtual_machine_test.go b/simulator/virtual_machine_test.go
index 1f7b4cb4..075695a6 100644
--- a/simulator/virtual_machine_test.go
+++ b/simulator/virtual_machine_test.go
@@ -390,6 +390,342 @@ func TestReconfigVmDevice(t *testing.T) {
 	}
 }
 
+func TestVAppConfigAdd(t *testing.T) {
+	ctx := context.Background()
+
+	m := ESX()
+	defer m.Remove()
+	err := m.Create()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	s := m.Service.NewServer()
+	defer s.Close()
+
+	c, err := govmomi.NewClient(ctx, s.URL, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	vmm := Map.Any("VirtualMachine").(*VirtualMachine)
+	vm := object.NewVirtualMachine(c.Client, vmm.Reference())
+
+	tests := []struct {
+		description      string
+		expectedErr      types.BaseMethodFault
+		spec             types.VirtualMachineConfigSpec
+		existingVMConfig *types.VirtualMachineConfigSpec
+		expectedProps    []types.VAppPropertyInfo
+	}{
+
+		{
+			description: "successfully add a new property",
+			spec: types.VirtualMachineConfigSpec{
+				VAppConfig: &types.VmConfigSpec{
+					Property: []types.VAppPropertySpec{
+						{
+							ArrayUpdateSpec: types.ArrayUpdateSpec{
+								Operation: types.ArrayUpdateOperationAdd,
+							},
+							Info: &types.VAppPropertyInfo{
+								Key:   int32(1),
+								Id:    "foo-id",
+								Value: "foo-value",
+							},
+						},
+					},
+				},
+			},
+			expectedProps: []types.VAppPropertyInfo{
+				{
+					Key:   int32(1),
+					Id:    "foo-id",
+					Value: "foo-value",
+				},
+			},
+		},
+		{
+			description: "return error when a property that exists is added",
+			expectedErr: new(types.InvalidArgument),
+			existingVMConfig: &types.VirtualMachineConfigSpec{
+				VAppConfig: &types.VmConfigSpec{
+					Property: []types.VAppPropertySpec{
+						{
+							ArrayUpdateSpec: types.ArrayUpdateSpec{
+								Operation: types.ArrayUpdateOperationAdd,
+							},
+							Info: &types.VAppPropertyInfo{
+								Key:   int32(2),
+								Id:    "foo-id",
+								Value: "foo-value",
+							},
+						},
+					},
+				},
+			},
+			spec: types.VirtualMachineConfigSpec{
+				VAppConfig: &types.VmConfigSpec{
+					Property: []types.VAppPropertySpec{
+						{
+							ArrayUpdateSpec: types.ArrayUpdateSpec{
+								Operation: types.ArrayUpdateOperationAdd,
+							},
+							Info: &types.VAppPropertyInfo{
+								Key: int32(2),
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, testCase := range tests {
+		t.Run(testCase.description, func(t *testing.T) {
+			if testCase.existingVMConfig != nil {
+				rtask, _ := vm.Reconfigure(ctx, *testCase.existingVMConfig)
+				if err := rtask.Wait(ctx); err != nil {
+					t.Errorf("Reconfigure failed during test setup. err: %v", err)
+				}
+			}
+
+			err := vmm.updateVAppProperty(testCase.spec.VAppConfig.GetVmConfigSpec())
+			if err != testCase.expectedErr {
+				t.Errorf("unexpected error in updating VApp property of VM. expectedErr: %v, actualErr: %v", testCase.expectedErr, err)
+			}
+
+			if testCase.expectedErr == nil {
+				props := vmm.Config.VAppConfig.GetVmConfigInfo().Property
+				// the testcase only has one VApp property, so ordering of the elements does not matter.
+				if !reflect.DeepEqual(props, testCase.expectedProps) {
+					t.Errorf("unexpected VApp properties. expected: %v, actual: %v", testCase.expectedProps, props)
+				}
+			}
+		})
+	}
+}
+
+func TestVAppConfigEdit(t *testing.T) {
+	ctx := context.Background()
+
+	m := ESX()
+	defer m.Remove()
+	err := m.Create()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	s := m.Service.NewServer()
+	defer s.Close()
+
+	c, err := govmomi.NewClient(ctx, s.URL, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	vmm := Map.Any("VirtualMachine").(*VirtualMachine)
+	vm := object.NewVirtualMachine(c.Client, vmm.Reference())
+
+	tests := []struct {
+		description      string
+		expectedErr      types.BaseMethodFault
+		spec             types.VirtualMachineConfigSpec
+		existingVMConfig *types.VirtualMachineConfigSpec
+		expectedProps    []types.VAppPropertyInfo
+	}{
+
+		{
+			description: "successfully update a property that exists",
+			existingVMConfig: &types.VirtualMachineConfigSpec{
+				VAppConfig: &types.VmConfigSpec{
+					Property: []types.VAppPropertySpec{
+						{
+							ArrayUpdateSpec: types.ArrayUpdateSpec{
+								Operation: types.ArrayUpdateOperationAdd,
+							},
+							Info: &types.VAppPropertyInfo{
+								Key:   int32(1),
+								Id:    "foo-id",
+								Value: "foo-value",
+							},
+						},
+					},
+				},
+			},
+			spec: types.VirtualMachineConfigSpec{
+				VAppConfig: &types.VmConfigSpec{
+					Property: []types.VAppPropertySpec{
+						{
+							ArrayUpdateSpec: types.ArrayUpdateSpec{
+								Operation: types.ArrayUpdateOperationEdit,
+							},
+							Info: &types.VAppPropertyInfo{
+								Key:   int32(1),
+								Id:    "foo-id-updated",
+								Value: "foo-value-updated",
+							},
+						},
+					},
+				},
+			},
+			expectedProps: []types.VAppPropertyInfo{
+				{
+					Key:   int32(1),
+					Id:    "foo-id-updated",
+					Value: "foo-value-updated",
+				},
+			},
+		},
+		{
+			description: "return error when a property that doesn't exist is updated",
+			expectedErr: new(types.InvalidArgument),
+			spec: types.VirtualMachineConfigSpec{
+				VAppConfig: &types.VmConfigSpec{
+					Property: []types.VAppPropertySpec{
+						{
+							ArrayUpdateSpec: types.ArrayUpdateSpec{
+								Operation: types.ArrayUpdateOperationEdit,
+							},
+							Info: &types.VAppPropertyInfo{
+								Key: int32(2),
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, testCase := range tests {
+		t.Run(testCase.description, func(t *testing.T) {
+			if testCase.existingVMConfig != nil {
+				rtask, _ := vm.Reconfigure(ctx, *testCase.existingVMConfig)
+				if err := rtask.Wait(ctx); err != nil {
+					t.Errorf("Reconfigure failed during test setup. err: %v", err)
+				}
+			}
+
+			err := vmm.updateVAppProperty(testCase.spec.VAppConfig.GetVmConfigSpec())
+			if err != testCase.expectedErr {
+				t.Errorf("unexpected error in updating VApp property of VM. expectedErr: %v, actualErr: %v", testCase.expectedErr, err)
+			}
+
+			if testCase.expectedErr == nil {
+				props := vmm.Config.VAppConfig.GetVmConfigInfo().Property
+				// the testcase only has one VApp property, so ordering of the elements does not matter.
+				if !reflect.DeepEqual(props, testCase.expectedProps) {
+					t.Errorf("unexpected VApp properties. expected: %v, actual: %v", testCase.expectedProps, props)
+				}
+			}
+		})
+	}
+}
+
+func TestVAppConfigRemove(t *testing.T) {
+	ctx := context.Background()
+
+	m := ESX()
+	defer m.Remove()
+	err := m.Create()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	s := m.Service.NewServer()
+	defer s.Close()
+
+	c, err := govmomi.NewClient(ctx, s.URL, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	vmm := Map.Any("VirtualMachine").(*VirtualMachine)
+	vm := object.NewVirtualMachine(c.Client, vmm.Reference())
+
+	tests := []struct {
+		description      string
+		expectedErr      types.BaseMethodFault
+		spec             types.VirtualMachineConfigSpec
+		existingVMConfig *types.VirtualMachineConfigSpec
+		expectedProps    []types.VAppPropertyInfo
+	}{
+		{
+			description: "returns success when a property that exists is removed",
+			existingVMConfig: &types.VirtualMachineConfigSpec{
+				VAppConfig: &types.VmConfigSpec{
+					Property: []types.VAppPropertySpec{
+						{
+							ArrayUpdateSpec: types.ArrayUpdateSpec{
+								Operation: types.ArrayUpdateOperationAdd,
+							},
+							Info: &types.VAppPropertyInfo{
+								Key: int32(1),
+							},
+						},
+					},
+				},
+			},
+			spec: types.VirtualMachineConfigSpec{
+				VAppConfig: &types.VmConfigSpec{
+					Property: []types.VAppPropertySpec{
+						{
+							ArrayUpdateSpec: types.ArrayUpdateSpec{
+								Operation: types.ArrayUpdateOperationRemove,
+							},
+							Info: &types.VAppPropertyInfo{
+								Key: int32(1),
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "return error when a property that doesn't exist is removed",
+			expectedErr: new(types.InvalidArgument),
+			spec: types.VirtualMachineConfigSpec{
+				VAppConfig: &types.VmConfigSpec{
+					Property: []types.VAppPropertySpec{
+						{
+							ArrayUpdateSpec: types.ArrayUpdateSpec{
+								Operation: types.ArrayUpdateOperationRemove,
+							},
+							Info: &types.VAppPropertyInfo{
+								Key: int32(2),
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, testCase := range tests {
+		t.Run(testCase.description, func(t *testing.T) {
+			if testCase.existingVMConfig != nil {
+				rtask, _ := vm.Reconfigure(ctx, *testCase.existingVMConfig)
+				if err := rtask.Wait(ctx); err != nil {
+					t.Errorf("Reconfigure failed during test setup. err: %v", err)
+				}
+			}
+
+			err := vmm.updateVAppProperty(testCase.spec.VAppConfig.GetVmConfigSpec())
+			if err != testCase.expectedErr {
+				t.Errorf("unexpected error in updating VApp property of VM. expectedErr: %v, actualErr: %v", testCase.expectedErr, err)
+			}
+
+			if testCase.expectedErr == nil {
+				props := vmm.Config.VAppConfig.GetVmConfigInfo().Property
+				// the testcase only has one VApp property, so ordering of the elements does not matter.
+				if !reflect.DeepEqual(props, testCase.expectedProps) {
+					t.Errorf("unexpected VApp properties. expected: %v, actual: %v", testCase.expectedProps, props)
+				}
+			}
+		})
+	}
+}
+
 func TestReconfigVm(t *testing.T) {
 	ctx := context.Background()