Repository: vmware/govmomi
Pull request #2590 (2021-09-15 22:23:08)

vcsim: Add TenantManager support in simulator
## Description

Add simple implementation of TenantManager in simulator to support
calls for marking, unmarking, retrieving service provider entities.

Adding support for TenantManager API (mark/unmark/retrieve "service provider" object)
in vcsim would help develop components that make use of TenantManager API functionality.

Closes: issue-2587

## 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

## How Has This Been Tested?
- [x] TestTenantManagerVPX
   - Added a new unit test that tests various API inputs for the TenantManager mark/unmark/retrieve APIs.
- [x] TestTenantManagerForOldClients
  - Added a new unit test to verify old vim client won't see TenantManager moref entry in ServiceContent but new vim clients would see it.
```
$ go test -v -run "TestTenantManager*" -failfast -v github.com/vmware/govmomi/simulator
=== RUN   TestTenantManagerForOldClients
--- PASS: TestTenantManagerForOldClients (0.38s)
=== RUN   TestTenantManagerVPX
--- PASS: TestTenantManagerVPX (0.38s)
PASS
ok  	github.com/vmware/govmomi/simulator	0.767s
```
- [x] Ran all `simulator` unit tests.
```
$ go test -failfast github.com/vmware/govmomi/simulator
ok  	github.com/vmware/govmomi/simulator	40.581s
```
- [x] Ran `govc-test` make target.
```
$ make govc-test
…
ok 172 vcsim powercli
…
ok 184 vcsim ovftool
…
ok 225 vm.customize
ok 226 volume.ls
ok 227 vsan.change

$ echo $?
0
```

**Test Configuration**:
* Toolchain: go version go1.17.1 linux/amd64
* SDK:
* (add more if needed)

## 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/object/tenant_manager.go b/object/tenant_manager.go
new file mode 100644
index 00000000..4dda196e
--- /dev/null
+++ b/object/tenant_manager.go
@@ -0,0 +1,78 @@
+/*
+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/model.go b/simulator/model.go
index 8c3883d3..faa15aaa 100644
--- a/simulator/model.go
+++ b/simulator/model.go
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2017 VMware, Inc. All Rights Reserved.
+Copyright (c) 2017-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.
@@ -257,6 +257,7 @@ 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(),
diff --git a/simulator/registry.go b/simulator/registry.go
index 654ef24a..8173b95b 100644
--- a/simulator/registry.go
+++ b/simulator/registry.go
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2017-2018 VMware, Inc. All Rights Reserved.
+Copyright (c) 2017-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.
@@ -563,6 +563,11 @@ 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/tenant_manager.go b/simulator/tenant_manager.go
new file mode 100644
index 00000000..51f06d61
--- /dev/null
+++ b/simulator/tenant_manager.go
@@ -0,0 +1,79 @@
+/*
+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
+}


Test patch:
diff --git a/simulator/tenant_manager_test.go b/simulator/tenant_manager_test.go
new file mode 100644
index 00000000..9e3dc8b1
--- /dev/null
+++ b/simulator/tenant_manager_test.go
@@ -0,0 +1,188 @@
+/*
+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)
+	}
+}