Files
tasks/common/membership/grpc_resolver_test.go
Yichao YangandGitHub ae221a7806 Switch to uber gomock (#6493)
## What changed?
<!-- Describe what has changed in this PR -->
- Switch to uber gomock
- Generate workflowservice and operatorservice mock inside server repo,
instead of using imports from api-go to avoid making breaking changes
there.

## Why?
<!-- Tell your future self why have you made these changes -->
- google gomock is no longer maintained and doesn't support generics

## How did you test it?
<!-- How have you verified this change? Tested locally? Added a unit
test? Checked in staging env? -->

## Potential risks
<!-- Assuming the worst case, what can be broken when deploying this
change to production? -->

## Documentation
<!-- Have you made sure this change doesn't falsify anything currently
stated in `docs/`? If significant
new behavior is added, have you described that in `docs/`? -->

## Is hotfix candidate?
<!-- Is this PR a hotfix candidate or does it require a notification to
be sent to the broader community? (Yes/No) -->
2024-09-09 14:44:20 -07:00

124 lines
4.8 KiB
Go

// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package membership
import (
"context"
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.temporal.io/server/common/primitives"
"go.temporal.io/server/internal/nettest"
"go.uber.org/mock/gomock"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func TestGRPCBuilder(t *testing.T) {
t.Parallel()
// There's a lot of channel stuff in this test, so we use a context to make sure we don't hang forever if something
// goes wrong.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
ctrl := gomock.NewController(t)
sr := NewMockServiceResolver(ctrl)
// On the first call to [ServiceResolver.Members], return an empty list of members
sr.EXPECT().AvailableMembers().Return([]HostInfo{})
// Once our resolver registers a listener to membership changes, get a hold of the channel it's listening on.
sr.EXPECT().AddListener(gomock.Any(), gomock.Any()).Do(func(_ string, ch chan<- *ChangedEvent) {
// Return a single member on the next call to [ServiceResolver.Members]. This simulates a temporary network
// partition where we can't find any hosts for the frontend for a short period of time, but then we get a host.
sr.EXPECT().AvailableMembers().Return([]HostInfo{
NewHostInfoFromAddress("localhost:1234"),
}).MinTimes(1) // MinTimes(1) because we don't control when ResolveNow is called
// After the first call to [ServiceResolver.Members] returns an empty list, expect our resolver to request a
// refresh of the members list. When it does, notify the listener that the members list has changed
sr.EXPECT().RequestRefresh().Do(func() {
select {
case <-ctx.Done():
case ch <- &ChangedEvent{}:
}
})
})
monitor := NewMockMonitor(ctrl)
monitor.EXPECT().GetResolver(primitives.FrontendService).Return(sr, nil)
// Start a fake local server and then dial it.
serverErrs := make(chan error)
p := nettest.NewPipe()
// This is our fake server. It accepts a connection and then immediately closes it.
go func() {
conn, _ := p.Accept(ctx.Done())
serverErrs <- conn.Close()
}()
// This is where we invoke the code under test. We dial the frontend service. The URL should use our custom
// protocol, and then our resolver should resolve this to the localhost:1234 address.
url := GRPCResolverURLForTesting(monitor, primitives.FrontendService)
assert.Regexp(t, "membership://frontend~0x[[:xdigit:]]*", url)
// dialedAddress is the actual address that the gRPC framework dialed after resolving the URL using our resolver.
var dialedAddress string
conn, err := grpc.NewClient(
url,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) {
dialedAddress = s
return p.Connect(ctx.Done())
}),
)
require.NoError(t, err)
conn.Connect()
require.NoError(t, <-serverErrs)
// The gRPC library calls [resolver.Resolver.Close] when the connection is closed in a background goroutine, so we
// can't synchronously assert that [ServiceResolver.RemoveListener] was called right after the connection is closed.
// Instead, we use a channel to signal that the listener was removed.
listenerRemoved := make(chan struct{})
sr.EXPECT().RemoveListener(gomock.Any()).Do(func(string) {
close(listenerRemoved)
})
assert.NoError(t, conn.Close())
select {
case <-ctx.Done():
t.Fatal("timed out waiting for resolver to be removed")
case <-listenerRemoved:
}
// Verify that the address we dialed was the address of the single host in the members list.
assert.Equal(t, "localhost:1234", dialedAddress)
}