[#16] Add pool tests with mock node

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2021-07-20 11:02:14 +03:00
parent 393034fd73
commit edc1382178
4 changed files with 343 additions and 6 deletions

11
go.mod
View file

@ -4,8 +4,15 @@ go 1.16
require (
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521073959-f0d4d129b7f1
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.5.4 // indirect
github.com/google/uuid v1.2.0
github.com/nspcc-dev/neo-go v0.95.3
github.com/nspcc-dev/neofs-api-go v1.28.3
github.com/stretchr/testify v1.6.1
github.com/stretchr/testify v1.7.0
go.uber.org/zap v1.10.0
google.golang.org/grpc v1.29.1
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
golang.org/x/text v0.3.4 // indirect
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect
google.golang.org/grpc v1.33.2
)

BIN
go.sum

Binary file not shown.

View file

@ -19,6 +19,11 @@ import (
"github.com/nspcc-dev/neofs-api-go/pkg/session"
)
// Client is a wrapper for client.Client to generate mock.
type Client interface {
client.Client
}
// BuilderOptions contains options used to build connection pool.
type BuilderOptions struct {
Key *ecdsa.PrivateKey
@ -28,6 +33,7 @@ type BuilderOptions struct {
SessionExpirationEpoch uint64
weights []float64
addresses []string
clientBuilder func(opts ...client.Option) (client.Client, error)
}
// Builder is an interim structure used to collect node addresses/weights and
@ -66,6 +72,11 @@ func (pb *Builder) Build(ctx context.Context, options *BuilderOptions) (Pool, er
options.weights = adjustWeights(pb.weights)
options.addresses = pb.addresses
if options.clientBuilder == nil {
options.clientBuilder = client.New
}
return newPool(ctx, options)
}
@ -96,7 +107,7 @@ type pool struct {
func newPool(ctx context.Context, options *BuilderOptions) (Pool, error) {
clientPacks := make([]*clientPack, len(options.weights))
for i, address := range options.addresses {
c, err := client.New(client.WithDefaultPrivateKey(options.Key),
c, err := options.clientBuilder(client.WithDefaultPrivateKey(options.Key),
client.WithURIAddress(address, nil),
client.WithDialTimeout(options.NodeConnectionTimeout))
if err != nil {
@ -139,9 +150,14 @@ func startRebalance(ctx context.Context, p *pool, options *BuilderOptions) {
ticker := time.NewTimer(options.ClientRebalanceInterval)
buffer := make([]float64, len(options.weights))
for range ticker.C {
updateNodesHealth(ctx, p, options, buffer)
ticker.Reset(options.ClientRebalanceInterval)
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
updateNodesHealth(ctx, p, options, buffer)
ticker.Reset(options.ClientRebalanceInterval)
}
}
}

314
pkg/pool/pool_test.go Normal file
View file

@ -0,0 +1,314 @@
package pool
import (
"context"
"fmt"
"math/rand"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-api-go/pkg/client"
"github.com/nspcc-dev/neofs-api-go/pkg/netmap"
"github.com/nspcc-dev/neofs-api-go/pkg/session"
"github.com/stretchr/testify/require"
)
func TestBuildPoolClientFailed(t *testing.T) {
key, err := keys.NewPrivateKey()
require.NoError(t, err)
clientBuilder := func(opts ...client.Option) (client.Client, error) {
return nil, fmt.Errorf("error")
}
pb := new(Builder)
pb.AddNode("peer0", 1)
opts := &BuilderOptions{
Key: &key.PrivateKey,
clientBuilder: clientBuilder,
}
_, err = pb.Build(context.TODO(), opts)
require.Error(t, err)
}
func TestBuildPoolCreateSessionFailed(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
key, err := keys.NewPrivateKey()
require.NoError(t, err)
ni := &netmap.NodeInfo{}
ni.SetAddresses("addr1", "addr2")
clientBuilder := func(opts ...client.Option) (client.Client, error) {
mockClient := NewMockClient(ctrl)
mockClient.EXPECT().CreateSession(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error session"))
mockClient.EXPECT().EndpointInfo(gomock.Any(), gomock.Any()).Return(&client.EndpointInfo{}, nil)
return mockClient, nil
}
pb := new(Builder)
pb.AddNode("peer0", 1)
opts := &BuilderOptions{
Key: &key.PrivateKey,
clientBuilder: clientBuilder,
}
_, err = pb.Build(context.TODO(), opts)
require.Error(t, err)
require.Contains(t, err.Error(), "client []: error session")
}
func TestBuildPoolZeroNodes(t *testing.T) {
key, err := keys.NewPrivateKey()
require.NoError(t, err)
pb := new(Builder)
opts := &BuilderOptions{
Key: &key.PrivateKey,
}
_, err = pb.Build(context.TODO(), opts)
require.Error(t, err)
}
func TestOneNode(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
tok := session.NewToken()
uid, err := uuid.New().MarshalBinary()
require.NoError(t, err)
tok.SetID(uid)
clientBuilder := func(opts ...client.Option) (client.Client, error) {
mockClient := NewMockClient(ctrl)
mockClient.EXPECT().CreateSession(gomock.Any(), gomock.Any()).Return(tok, nil)
return mockClient, nil
}
key, err := keys.NewPrivateKey()
require.NoError(t, err)
pb := new(Builder)
pb.AddNode("peer0", 1)
opts := &BuilderOptions{
Key: &key.PrivateKey,
clientBuilder: clientBuilder,
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
pool, err := pb.Build(ctx, opts)
require.NoError(t, err)
_, st, err := pool.Connection()
require.NoError(t, err)
require.Equal(t, tok, st)
}
func TestTwoNodes(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
var tokens []*session.Token
clientBuilder := func(opts ...client.Option) (client.Client, error) {
mockClient := NewMockClient(ctrl)
mockClient.EXPECT().CreateSession(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}, _ ...interface{}) (*session.Token, error) {
tok := session.NewToken()
uid, err := uuid.New().MarshalBinary()
require.NoError(t, err)
tok.SetID(uid)
tokens = append(tokens, tok)
return tok, err
})
return mockClient, nil
}
key, err := keys.NewPrivateKey()
require.NoError(t, err)
pb := new(Builder)
pb.AddNode("peer0", 1)
pb.AddNode("peer1", 1)
opts := &BuilderOptions{
Key: &key.PrivateKey,
clientBuilder: clientBuilder,
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
pool, err := pb.Build(ctx, opts)
require.NoError(t, err)
_, st, err := pool.Connection()
require.NoError(t, err)
require.Contains(t, tokens, st)
}
func TestOneOfTwoFailed(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctrl2 := gomock.NewController(t)
defer ctrl2.Finish()
var tokens []*session.Token
clientCount := -1
clientBuilder := func(opts ...client.Option) (client.Client, error) {
clientCount++
mockClient := NewMockClient(ctrl)
mockClient.EXPECT().CreateSession(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}, _ ...interface{}) (*session.Token, error) {
tok := newToken(t)
tokens = append(tokens, tok)
return tok, nil
}).AnyTimes()
mockClient.EXPECT().EndpointInfo(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
mockClient2 := NewMockClient(ctrl2)
mockClient2.EXPECT().CreateSession(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}, _ ...interface{}) (*session.Token, error) {
tok := newToken(t)
tokens = append(tokens, tok)
return tok, nil
}).AnyTimes()
mockClient2.EXPECT().EndpointInfo(gomock.Any(), gomock.Any()).DoAndReturn(func(_ interface{}, _ ...interface{}) (*client.EndpointInfo, error) {
return nil, fmt.Errorf("error")
}).AnyTimes()
if clientCount == 0 {
return mockClient, nil
}
return mockClient2, nil
}
key, err := keys.NewPrivateKey()
require.NoError(t, err)
pb := new(Builder)
pb.AddNode("peer0", 1)
pb.AddNode("peer1", 9)
opts := &BuilderOptions{
Key: &key.PrivateKey,
clientBuilder: clientBuilder,
ClientRebalanceInterval: 200 * time.Millisecond,
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
pool, err := pb.Build(ctx, opts)
require.NoError(t, err)
time.Sleep(2 * time.Second)
for i := 0; i < 5; i++ {
_, st, err := pool.Connection()
require.NoError(t, err)
require.Equal(t, tokens[0], st)
}
}
func TestTwoFailed(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
clientBuilder := func(opts ...client.Option) (client.Client, error) {
mockClient := NewMockClient(ctrl)
mockClient.EXPECT().CreateSession(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
mockClient.EXPECT().EndpointInfo(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")).AnyTimes()
return mockClient, nil
}
key, err := keys.NewPrivateKey()
require.NoError(t, err)
pb := new(Builder)
pb.AddNode("peer0", 1)
pb.AddNode("peer1", 1)
opts := &BuilderOptions{
Key: &key.PrivateKey,
clientBuilder: clientBuilder,
ClientRebalanceInterval: 200 * time.Millisecond,
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
pool, err := pb.Build(ctx, opts)
require.NoError(t, err)
time.Sleep(2 * time.Second)
_, _, err = pool.Connection()
require.Error(t, err)
require.Contains(t, err.Error(), "no healthy")
}
func newToken(t *testing.T) *session.Token {
tok := session.NewToken()
uid, err := uuid.New().MarshalBinary()
require.NoError(t, err)
tok.SetID(uid)
return tok
}
func TestWaitPresence(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockClient := NewMockClient(ctrl)
mockClient.EXPECT().CreateSession(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
mockClient.EXPECT().EndpointInfo(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
mockClient.EXPECT().GetContainer(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
p := &pool{
sampler: NewSampler([]float64{1}, rand.NewSource(0)),
clientPacks: []*clientPack{{
client: mockClient,
healthy: true,
}},
}
t.Run("context canceled", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(500 * time.Millisecond)
cancel()
}()
err := p.WaitForContainerPresence(ctx, nil, DefaultPollingParams())
require.Error(t, err)
require.Contains(t, err.Error(), "context canceled")
})
t.Run("context deadline exceeded", func(t *testing.T) {
ctx := context.Background()
err := p.WaitForContainerPresence(ctx, nil, &ContainerPollingParams{
CreationTimeout: 500 * time.Millisecond,
PollInterval: 5 * time.Second,
})
require.Error(t, err)
require.Contains(t, err.Error(), "context deadline exceeded")
})
t.Run("ok", func(t *testing.T) {
ctx := context.Background()
err := p.WaitForContainerPresence(ctx, nil, &ContainerPollingParams{
CreationTimeout: 10 * time.Second,
PollInterval: 500 * time.Millisecond,
})
require.NoError(t, err)
})
}