forked from TrueCloudLab/frostfs-sdk-go
[#16] Add pool tests with mock node
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
393034fd73
commit
edc1382178
4 changed files with 343 additions and 6 deletions
11
go.mod
11
go.mod
|
@ -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
BIN
go.sum
Binary file not shown.
|
@ -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
314
pkg/pool/pool_test.go
Normal 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)
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue