Repository: vmware/govmomi
Pull request #2456 (2021-05-21 19:26:43)

Support pre-auth handlers in vC Sim
## Description

This patch adds support for pre-auth handlers in vC Sim. This enables SDKs registered with vC Sim to return their own errors if a session does not exist.

This patch includes a validation of this behavior by asserting the EAM simulator should return `EamInvalidLogin` when a session is not present instead of `vim.NotAuthenticated`.

Closes: _NA_

## 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?

```shell
$ go test -v -run TestNotAuthenticated -count 1 ./eam/simulator
=== RUN   TestNotAuthenticated
=== RUN   TestNotAuthenticated/TerminateSession
=== RUN   TestNotAuthenticated/ValidateFaults
=== RUN   TestNotAuthenticated/ValidateFaults/vim.NotAuthenticated
=== PAUSE TestNotAuthenticated/ValidateFaults/vim.NotAuthenticated
=== RUN   TestNotAuthenticated/ValidateFaults/eam.EamInvalidLogin
=== PAUSE TestNotAuthenticated/ValidateFaults/eam.EamInvalidLogin
=== CONT  TestNotAuthenticated/ValidateFaults/vim.NotAuthenticated
=== CONT  TestNotAuthenticated/ValidateFaults/eam.EamInvalidLogin
--- PASS: TestNotAuthenticated (0.40s)
    --- PASS: TestNotAuthenticated/TerminateSession (0.00s)
    --- PASS: TestNotAuthenticated/ValidateFaults (0.00s)
        --- PASS: TestNotAuthenticated/ValidateFaults/vim.NotAuthenticated (0.00s)
        --- PASS: TestNotAuthenticated/ValidateFaults/eam.EamInvalidLogin (0.00s)
PASS
ok  	github.com/vmware/govmomi/eam/simulator	0.757s
```

## 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
- [x] ~~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
- [x] ~~Any dependent changes have been merged~~


Patch:
diff --git a/eam/simulator/agent.go b/eam/simulator/agent.go
index 2ed8e886..d055cc9d 100644
--- a/eam/simulator/agent.go
+++ b/eam/simulator/agent.go
@@ -161,9 +161,8 @@ func NewAgent(
 	}
 
 	// Start watching this VM and updating the agent's information about the VM.
-	go func(eamReg, vimReg *simulator.Registry) {
+	go func(ctx *simulator.Context, eamReg, vimReg *simulator.Registry) {
 		var (
-			ctx    = simulator.SpoofContext()
 			ticker = time.NewTicker(1 * time.Second)
 			vmName string
 		)
@@ -229,7 +228,7 @@ func NewAgent(
 				})
 			})
 		}
-	}(ctx.Map, vimMap)
+	}(simulator.SpoofContext(), ctx.Map, vimMap)
 
 	return agent, nil
 }
diff --git a/eam/simulator/simulator.go b/eam/simulator/simulator.go
index 19f01c7a..e4494dbe 100644
--- a/eam/simulator/simulator.go
+++ b/eam/simulator/simulator.go
@@ -18,7 +18,10 @@ package simulator
 
 import (
 	"github.com/vmware/govmomi/eam"
+	"github.com/vmware/govmomi/eam/types"
 	"github.com/vmware/govmomi/simulator"
+	vimmo "github.com/vmware/govmomi/vim25/mo"
+	vim "github.com/vmware/govmomi/vim25/types"
 )
 
 func init() {
@@ -33,6 +36,7 @@ func New() *simulator.Registry {
 	r := simulator.NewRegistry()
 	r.Namespace = eam.Namespace
 	r.Path = eam.Path
+	r.Handler = invalidLoginForNilSessionFn
 
 	r.Put(&EsxAgentManager{
 		EamObject: EamObject{
@@ -42,3 +46,16 @@ func New() *simulator.Registry {
 
 	return r
 }
+
+// invalidLoginForNilSessionFn returns EamInvalidLogin if the provided
+// ctx.Session is nil. This is for validating all calls to EAM methods
+// have a valid credential.
+func invalidLoginForNilSessionFn(
+	ctx *simulator.Context,
+	_ *simulator.Method) (vimmo.Reference, vim.BaseMethodFault) {
+
+	if ctx.Session == nil {
+		return nil, new(types.EamInvalidLogin)
+	}
+	return nil, nil
+}
diff --git a/simulator/registry.go b/simulator/registry.go
index 8f0334cb..207a3c49 100644
--- a/simulator/registry.go
+++ b/simulator/registry.go
@@ -70,6 +70,7 @@ type Registry struct {
 
 	Namespace string
 	Path      string
+	Handler   func(*Context, *Method) (mo.Reference, types.BaseMethodFault)
 
 	tagManager tagManager
 }
diff --git a/simulator/simulator.go b/simulator/simulator.go
index b26451ae..11fe09f4 100644
--- a/simulator/simulator.go
+++ b/simulator/simulator.go
@@ -140,6 +140,16 @@ func (s *Service) call(ctx *Context, method *Method) soap.HasFault {
 	session := ctx.Session
 	ctx.Caller = &method.This
 
+	if ctx.Map.Handler != nil {
+		h, fault := ctx.Map.Handler(ctx, method)
+		if fault != nil {
+			return &serverFaultBody{Reason: Fault("", fault)}
+		}
+		if h != nil {
+			handler = h
+		}
+	}
+
 	if session == nil {
 		switch method.Name {
 		case "RetrieveServiceContent", "PbmRetrieveServiceContent", "Fetch", "List", "Login", "LoginByToken", "LoginExtensionByCertificate", "RetrieveProperties", "RetrievePropertiesEx", "CloneSession":


Test patch:
diff --git a/eam/simulator/simulator_test.go b/eam/simulator/simulator_test.go
index 7232a117..f901a648 100644
--- a/eam/simulator/simulator_test.go
+++ b/eam/simulator/simulator_test.go
@@ -31,12 +31,22 @@ import (
 	"github.com/vmware/govmomi/eam/types"
 	"github.com/vmware/govmomi/find"
 	vimobject "github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/session"
 	vcsim "github.com/vmware/govmomi/simulator"
 	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/soap"
 	vim "github.com/vmware/govmomi/vim25/types"
 )
 
+const waitLoopMessage = `
+
+################################################################################
+# When executed with the flags -powerOnVMs and -waitForExit, this test will    #
+# pause here and update the screen with the status of the agent VMs until      #
+# SIGINT is sent to this process.                                              #
+################################################################################
+`
+
 var (
 	flagPowerOnVMs = flag.Bool("powerOnVMs", false, "Powers on the VMs in the test with Docker")
 	flagWaitToExit = flag.Bool("waitToExit", false, "Waits for user input to exit the test")
@@ -332,11 +342,106 @@ func TestSimulator(t *testing.T) {
 	})
 }
 
-const waitLoopMessage = `
+func TestNotAuthenticated(t *testing.T) {
+	vcsim.Test(func(ctx context.Context, vimClient *vim25.Client) {
 
-################################################################################
-# When executed with the flags -powerOnVMs and -waitForExit, this test will    #
-# pause here and update the screen with the status of the agent VMs until      #
-# SIGINT is sent to this process.                                              #
-################################################################################
-`
+		isNotAuthenticatedFault := func(fault vim.AnyType) bool {
+			switch fault.(type) {
+			case vim.NotAuthenticated, *vim.NotAuthenticated:
+				return true
+			default:
+				return false
+			}
+		}
+
+		isEamInvalidLoginFault := func(fault vim.AnyType) bool {
+			switch fault.(type) {
+			case types.EamInvalidLogin, *types.EamInvalidLogin:
+				return true
+			default:
+				return false
+			}
+		}
+
+		validateFault := func(
+			err error,
+			faultType string,
+			faultIsTypeFn func(vim.AnyType) bool) error {
+
+			if err == nil {
+				return fmt.Errorf("%s expected", faultType)
+			}
+
+			validateVimFault := func(vimFault vim.AnyType) error {
+				if !faultIsTypeFn(vimFault) {
+					return fmt.Errorf(
+						"err is not %[1]s: %[2]T %[2]v", faultType, vimFault)
+				}
+				return nil
+			}
+
+			if soap.IsSoapFault(err) {
+				return validateVimFault(soap.ToSoapFault(err).VimFault())
+			}
+
+			if soap.IsVimFault(err) {
+				return validateVimFault(soap.ToVimFault(err))
+			}
+
+			return fmt.Errorf(
+				"err is not a Soap or Vim fault: %[1]T %[1]v", err)
+		}
+
+		t.Run("TerminateSession", func(t *testing.T) {
+			// Terminate the session.
+			sessionManager := session.NewManager(vimClient)
+			if err := sessionManager.Logout(ctx); err != nil {
+				t.Fatalf("logout failed: %v", err)
+			}
+		})
+
+		t.Run("ValidateFaults", func(t *testing.T) {
+
+			t.Run("vim.NotAuthenticated", func(t *testing.T) {
+				t.Parallel()
+
+				// Create a finder to get the default datacenter.
+				finder := find.NewFinder(vimClient, true)
+
+				// Try to get the default datacenter, but receive a NotAuthenticated
+				// error.
+				_, err := finder.DefaultDatacenter(ctx)
+
+				if err := validateFault(
+					err,
+					"NotAuthenticated",
+					isNotAuthenticatedFault); err != nil {
+
+					t.Fatal(err)
+				}
+			})
+
+			t.Run("eam.EamInvalidLogin", func(t *testing.T) {
+				t.Parallel()
+
+				// Get an EAM client.
+				eamClient := eam.NewClient(vimClient)
+
+				// Get the EAM root object.
+				mgr := object.NewEsxAgentManager(eamClient, eam.EsxAgentManager)
+
+				// Try to list the agencies, but receive an EamInvalidLogin error.
+				_, err := mgr.Agencies(ctx)
+
+				if err := validateFault(
+					err,
+					"EamInvalidLogin",
+					isEamInvalidLoginFault); err != nil {
+
+					t.Fatal(err)
+				}
+			})
+		})
+
+	})
+}