From 09015cd7f7594a085c5d9d0649dd9f3e309dc86c Mon Sep 17 00:00:00 2001 From: Albert Skalt Date: Tue, 7 Nov 2023 15:35:02 +0300 Subject: [PATCH 1/4] api: make dialer mandatory This patch modifies `Connect` api. Now, to connect to the Tarantool, you need to pass an object that satisfies `tarantool.Dialer` interface. You can use one of the existing implementations: `NetDialer` or `OpenSslDialer`. For example: ``` conn, err := tarantool.Connect(context.Background(), tarantool.NetDialer{ Address: "127.0.0.1:3301", User: "user", Password: "secret", }, tarantool.Opts{}) ``` To create a connection pool, you need to pass a `map[string]tarantool.Dialer`, where each dialer is associated with a unique ID (for example, it can be the server address). Dialers will be distinguished from each other using these IDs. For example: ``` connPool, err := pool.Connect(context.Background(), map[string]tarantool.Dialer{ "127.0.0.1": tarantool.NetDialer{ Address: "127.0.0.1", User: "user", Password: "secret", }, }, tarantool.Opts{}) ``` The `conn.RemoteAddr` and `conn.LocalAddr` functions have been removed. To obtain the connection address, you can use `conn.Addr`. Now, `NewWatcher` checks the actual features of the server, rather than relying on the features provided by the user during connection creation. In the case of connection pool, watchers are created for connections that support this feature. `ClientProtocolInfo`, `ServerProtocolInfo` were removed. Now, there is `ProtocolInfo`, which returns the server protocol info. `pool.GetPoolInfo` was renamed to `pool.GetInfo`. Return type changed to `map[string]ConnectionInfo`. Part of #321 --- box_error_test.go | 12 +- connection.go | 148 ++----- connection_test.go | 32 -- crud/example_test.go | 10 +- crud/tarantool_test.go | 14 +- datetime/datetime_test.go | 28 +- datetime/example_test.go | 10 +- datetime/interval_test.go | 2 +- decimal/decimal_test.go | 18 +- decimal/example_test.go | 9 +- dial.go | 254 ++++++++---- dial_test.go | 574 ++++++++++++++++++++++---- example_custom_unpacking_test.go | 13 +- example_test.go | 173 ++++---- export_test.go | 16 +- pool/connection_pool.go | 356 ++++++++-------- pool/connection_pool_test.go | 500 +++++++++++----------- pool/example_test.go | 87 ++-- pool/round_robin.go | 48 +-- pool/round_robin_test.go | 15 +- pool/watcher.go | 14 +- queue/example_connection_pool_test.go | 28 +- queue/example_msgpack_test.go | 9 +- queue/example_test.go | 9 +- queue/queue_test.go | 93 +++-- request.go | 26 +- settings/example_test.go | 16 +- settings/tarantool_test.go | 34 +- shutdown_test.go | 58 +-- ssl.go | 33 +- ssl_test.go | 367 ++++++---------- tarantool_test.go | 405 ++++++------------ test_helpers/main.go | 47 +-- test_helpers/pool_helper.go | 57 ++- test_helpers/utils.go | 10 +- uuid/example_test.go | 13 +- uuid/uuid_test.go | 14 +- 37 files changed, 1861 insertions(+), 1691 deletions(-) delete mode 100644 connection_test.go diff --git a/box_error_test.go b/box_error_test.go index 0d7d3f47b..b08b6cc52 100644 --- a/box_error_test.go +++ b/box_error_test.go @@ -299,7 +299,7 @@ func TestErrorTypeMPEncodeDecode(t *testing.T) { func TestErrorTypeEval(t *testing.T) { test_helpers.SkipIfErrorMessagePackTypeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() for name, testcase := range tupleCases { @@ -318,7 +318,7 @@ func TestErrorTypeEval(t *testing.T) { func TestErrorTypeEvalTyped(t *testing.T) { test_helpers.SkipIfErrorMessagePackTypeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() for name, testcase := range tupleCases { @@ -336,7 +336,7 @@ func TestErrorTypeEvalTyped(t *testing.T) { func TestErrorTypeInsert(t *testing.T) { test_helpers.SkipIfErrorMessagePackTypeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() truncateEval := fmt.Sprintf("box.space[%q]:truncate()", space) @@ -374,7 +374,7 @@ func TestErrorTypeInsert(t *testing.T) { func TestErrorTypeInsertTyped(t *testing.T) { test_helpers.SkipIfErrorMessagePackTypeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() truncateEval := fmt.Sprintf("box.space[%q]:truncate()", space) @@ -416,7 +416,7 @@ func TestErrorTypeInsertTyped(t *testing.T) { func TestErrorTypeSelect(t *testing.T) { test_helpers.SkipIfErrorMessagePackTypeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() truncateEval := fmt.Sprintf("box.space[%q]:truncate()", space) @@ -461,7 +461,7 @@ func TestErrorTypeSelect(t *testing.T) { func TestErrorTypeSelectTyped(t *testing.T) { test_helpers.SkipIfErrorMessagePackTypeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() truncateEval := fmt.Sprintf("box.space[%q]:truncate()", space) diff --git a/connection.go b/connection.go index 48de5476b..a92a66d84 100644 --- a/connection.go +++ b/connection.go @@ -10,6 +10,7 @@ import ( "io" "log" "math" + "net" "runtime" "sync" "sync/atomic" @@ -90,15 +91,15 @@ func (d defaultLogger) Report(event ConnLogKind, conn *Connection, v ...interfac reconnects := v[0].(uint) err := v[1].(error) log.Printf("tarantool: reconnect (%d/%d) to %s failed: %s", - reconnects, conn.opts.MaxReconnects, conn.addr, err) + reconnects, conn.opts.MaxReconnects, conn.Addr(), err) case LogLastReconnectFailed: err := v[0].(error) log.Printf("tarantool: last reconnect to %s failed: %s, giving it up", - conn.addr, err) + conn.Addr(), err) case LogUnexpectedResultId: resp := v[0].(*Response) log.Printf("tarantool: connection %s got unexpected resultId (%d) in response", - conn.addr, resp.RequestId) + conn.Addr(), resp.RequestId) case LogWatchEventReadFailed: err := v[0].(error) log.Printf("tarantool: unable to parse watch event: %s", err) @@ -156,10 +157,11 @@ func (d defaultLogger) Report(event ConnLogKind, conn *Connection, v ...interfac // More on graceful shutdown: // https://www.tarantool.io/en/doc/latest/dev_guide/internals/iproto/graceful_shutdown/ type Connection struct { - addr string - c Conn - mutex sync.Mutex - cond *sync.Cond + addr net.Addr + dialer Dialer + c Conn + mutex sync.Mutex + cond *sync.Cond // schemaResolver contains a SchemaResolver implementation. schemaResolver SchemaResolver // requestId contains the last request ID for requests with nil context. @@ -260,11 +262,6 @@ const ( // Opts is a way to configure Connection type Opts struct { - // Auth is an authentication method. - Auth Auth - // Dialer is a Dialer object used to create a new connection to a - // Tarantool instance. TtDialer is a default one. - Dialer Dialer // Timeout for response to a particular request. The timeout is reset when // push messages are received. If Timeout is zero, any request can be // blocked infinitely. @@ -287,10 +284,6 @@ type Opts struct { // endlessly. // After MaxReconnects attempts Connection becomes closed. MaxReconnects uint - // Username for logging in to Tarantool. - User string - // User password for logging in to Tarantool. - Pass string // RateLimit limits number of 'in-fly' request, i.e. already put into // requests queue, but not yet answered by server or timeouted. // It is disabled by default. @@ -315,83 +308,23 @@ type Opts struct { Handle interface{} // Logger is user specified logger used for error messages. Logger Logger - // Transport is the connection type, by default the connection is unencrypted. - Transport string - // SslOpts is used only if the Transport == 'ssl' is set. - Ssl SslOpts - // RequiredProtocolInfo contains minimal protocol version and - // list of protocol features that should be supported by - // Tarantool server. By default there are no restrictions. - RequiredProtocolInfo ProtocolInfo -} - -// SslOpts is a way to configure ssl transport. -type SslOpts struct { - // KeyFile is a path to a private SSL key file. - KeyFile string - // CertFile is a path to an SSL certificate file. - CertFile string - // CaFile is a path to a trusted certificate authorities (CA) file. - CaFile string - // Ciphers is a colon-separated (:) list of SSL cipher suites the connection - // can use. - // - // We don't provide a list of supported ciphers. This is what OpenSSL - // does. The only limitation is usage of TLSv1.2 (because other protocol - // versions don't seem to support the GOST cipher). To add additional - // ciphers (GOST cipher), you must configure OpenSSL. - // - // See also - // - // * https://www.openssl.org/docs/man1.1.1/man1/ciphers.html - Ciphers string - // Password is a password for decrypting the private SSL key file. - // The priority is as follows: try to decrypt with Password, then - // try PasswordFile. - Password string - // PasswordFile is a path to the list of passwords for decrypting - // the private SSL key file. The connection tries every line from the - // file as a password. - PasswordFile string -} - -// Clone returns a copy of the Opts object. -// Any changes in copy RequiredProtocolInfo will not affect the original -// RequiredProtocolInfo value. -func (opts Opts) Clone() Opts { - optsCopy := opts - optsCopy.RequiredProtocolInfo = opts.RequiredProtocolInfo.Clone() - - return optsCopy } // Connect creates and configures a new Connection. -// -// Address could be specified in following ways: -// -// - TCP connections (tcp://192.168.1.1:3013, tcp://my.host:3013, -// tcp:192.168.1.1:3013, tcp:my.host:3013, 192.168.1.1:3013, my.host:3013) -// -// - Unix socket, first '/' or '.' indicates Unix socket -// (unix:///abs/path/tnt.sock, unix:path/tnt.sock, /abs/path/tnt.sock, -// ./rel/path/tnt.sock, unix/:path/tnt.sock) -func Connect(ctx context.Context, addr string, opts Opts) (conn *Connection, err error) { +func Connect(ctx context.Context, dialer Dialer, opts Opts) (conn *Connection, err error) { conn = &Connection{ - addr: addr, + dialer: dialer, requestId: 0, contextRequestId: 1, Greeting: &Greeting{}, control: make(chan struct{}), - opts: opts.Clone(), + opts: opts, dec: msgpack.NewDecoder(&smallBuf{}), } maxprocs := uint32(runtime.GOMAXPROCS(-1)) if conn.opts.Concurrency == 0 || conn.opts.Concurrency > maxprocs*128 { conn.opts.Concurrency = maxprocs * 4 } - if conn.opts.Dialer == nil { - conn.opts.Dialer = TtDialer{} - } if c := conn.opts.Concurrency; c&(c-1) != 0 { for i := uint(1); i < 32; i *= 2 { c |= c >> i @@ -474,30 +407,10 @@ func (conn *Connection) CloseGraceful() error { } // Addr returns a configured address of Tarantool socket. -func (conn *Connection) Addr() string { +func (conn *Connection) Addr() net.Addr { return conn.addr } -// RemoteAddr returns an address of Tarantool socket. -func (conn *Connection) RemoteAddr() string { - conn.mutex.Lock() - defer conn.mutex.Unlock() - if conn.c == nil { - return "" - } - return conn.c.RemoteAddr().String() -} - -// LocalAddr returns an address of outgoing socket. -func (conn *Connection) LocalAddr() string { - conn.mutex.Lock() - defer conn.mutex.Unlock() - if conn.c == nil { - return "" - } - return conn.c.LocalAddr().String() -} - // Handle returns a user-specified handle from Opts. func (conn *Connection) Handle() interface{} { return conn.opts.Handle @@ -514,19 +427,14 @@ func (conn *Connection) dial(ctx context.Context) error { opts := conn.opts var c Conn - c, err := conn.opts.Dialer.Dial(ctx, conn.addr, DialOpts{ - IoTimeout: opts.Timeout, - Transport: opts.Transport, - Ssl: opts.Ssl, - RequiredProtocol: opts.RequiredProtocolInfo, - Auth: opts.Auth, - User: opts.User, - Password: opts.Pass, + c, err := conn.dialer.Dial(ctx, DialOpts{ + IoTimeout: opts.Timeout, }) if err != nil { return err } + conn.addr = c.Addr() conn.Greeting.Version = c.Greeting().Version conn.serverProtocolInfo = c.ProtocolInfo() @@ -1453,8 +1361,7 @@ func isFeatureInSlice(expected iproto.Feature, actualSlice []iproto.Feature) boo // NewWatcher creates a new Watcher object for the connection. // -// You need to require IPROTO_FEATURE_WATCHERS to use watchers, see examples -// for the function. +// Server must support IPROTO_FEATURE_WATCHERS to use watchers. // // After watcher creation, the watcher callback is invoked for the first time. // In this case, the callback is triggered whether or not the key has already @@ -1490,9 +1397,9 @@ func (conn *Connection) NewWatcher(key string, callback WatchCallback) (Watcher, // That's why we can't just check the Tarantool response for an unsupported // request error. if !isFeatureInSlice(iproto.IPROTO_FEATURE_WATCHERS, - conn.opts.RequiredProtocolInfo.Features) { - err := fmt.Errorf("the feature %s must be required by connection "+ - "options to create a watcher", iproto.IPROTO_FEATURE_WATCHERS) + conn.c.ProtocolInfo().Features) { + err := fmt.Errorf("the feature %s must be supported by connection "+ + "to create a watcher", iproto.IPROTO_FEATURE_WATCHERS) return nil, err } @@ -1583,23 +1490,14 @@ func (conn *Connection) newWatcherImpl(key string, callback WatchCallback) (Watc }, nil } -// ServerProtocolVersion returns protocol version and protocol features +// ProtocolInfo returns protocol version and protocol features // supported by connected Tarantool server. Beware that values might be // outdated if connection is in a disconnected state. -// Since 1.10.0 -func (conn *Connection) ServerProtocolInfo() ProtocolInfo { +// Since 2.0.0 +func (conn *Connection) ProtocolInfo() ProtocolInfo { return conn.serverProtocolInfo.Clone() } -// ClientProtocolVersion returns protocol version and protocol features -// supported by Go connection client. -// Since 1.10.0 -func (conn *Connection) ClientProtocolInfo() ProtocolInfo { - info := clientProtocolInfo.Clone() - info.Auth = conn.opts.Auth - return info -} - func shutdownEventCallback(event WatchEvent) { // Receives "true" on server shutdown. // See https://www.tarantool.io/en/doc/latest/dev_guide/internals/iproto/graceful_shutdown/ diff --git a/connection_test.go b/connection_test.go deleted file mode 100644 index 20d06b183..000000000 --- a/connection_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package tarantool_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/tarantool/go-iproto" - - . "github.com/tarantool/go-tarantool/v2" -) - -func TestOptsClonePreservesRequiredProtocolFeatures(t *testing.T) { - original := Opts{ - RequiredProtocolInfo: ProtocolInfo{ - Version: ProtocolVersion(100), - Features: []iproto.Feature{iproto.Feature(99), iproto.Feature(100)}, - }, - } - - origCopy := original.Clone() - - original.RequiredProtocolInfo.Features[1] = iproto.Feature(98) - - require.Equal(t, - origCopy, - Opts{ - RequiredProtocolInfo: ProtocolInfo{ - Version: ProtocolVersion(100), - Features: []iproto.Feature{iproto.Feature(99), iproto.Feature(100)}, - }, - }) -} diff --git a/crud/example_test.go b/crud/example_test.go index 763ab5deb..c043bd5f0 100644 --- a/crud/example_test.go +++ b/crud/example_test.go @@ -17,14 +17,18 @@ const ( var exampleOpts = tarantool.Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", +} + +var exampleDialer = tarantool.NetDialer{ + Address: exampleServer, + User: "test", + Password: "test", } func exampleConnect() *tarantool.Connection { ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() - conn, err := tarantool.Connect(ctx, exampleServer, exampleOpts) + conn, err := tarantool.Connect(ctx, exampleDialer, exampleOpts) if err != nil { panic("Connection is not established: " + err.Error()) } diff --git a/crud/tarantool_test.go b/crud/tarantool_test.go index 9f6b5d948..dfc8d064e 100644 --- a/crud/tarantool_test.go +++ b/crud/tarantool_test.go @@ -21,17 +21,21 @@ var spaceName = "test" var invalidSpaceName = "invalid" var indexNo = uint32(0) var indexName = "primary_index" + +var dialer = tarantool.NetDialer{ + Address: server, + User: "test", + Password: "test", +} + var opts = tarantool.Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", } var startOpts test_helpers.StartOpts = test_helpers.StartOpts{ + Dialer: dialer, InitScript: "testdata/config.lua", Listen: server, - User: opts.User, - Pass: opts.Pass, WaitStart: 100 * time.Millisecond, ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, @@ -171,7 +175,7 @@ var object = crud.MapObject{ func connect(t testing.TB) *tarantool.Connection { for i := 0; i < 10; i++ { ctx, cancel := test_helpers.GetConnectContext() - conn, err := tarantool.Connect(ctx, server, opts) + conn, err := tarantool.Connect(ctx, dialer, opts) cancel() if err != nil { t.Fatalf("Failed to connect: %s", err) diff --git a/datetime/datetime_test.go b/datetime/datetime_test.go index 858c7b84a..630d5f062 100644 --- a/datetime/datetime_test.go +++ b/datetime/datetime_test.go @@ -40,8 +40,11 @@ var isDatetimeSupported = false var server = "127.0.0.1:3013" var opts = Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", +} +var dialer = NetDialer{ + Address: server, + User: "test", + Password: "test", } var spaceTuple1 = "testDatetime_1" @@ -364,7 +367,7 @@ func TestDatetimeInterval(t *testing.T) { func TestDatetimeTarantoolInterval(t *testing.T) { skipIfDatetimeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() dates := []string{ @@ -504,7 +507,7 @@ func TestInvalidOffset(t *testing.T) { t.Fatalf("Unexpected success: %v", dt) } if testcase.ok && isDatetimeSupported { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() tupleInsertSelectDelete(t, conn, tm) @@ -516,7 +519,7 @@ func TestInvalidOffset(t *testing.T) { func TestCustomTimezone(t *testing.T) { skipIfDatetimeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() customZone := "Europe/Moscow" @@ -676,7 +679,7 @@ var datetimeSample = []struct { func TestDatetimeInsertSelectDelete(t *testing.T) { skipIfDatetimeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() for _, testcase := range datetimeSample { @@ -705,7 +708,7 @@ func TestDatetimeInsertSelectDelete(t *testing.T) { func TestDatetimeBoundaryRange(t *testing.T) { skipIfDatetimeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() for _, tm := range append(lesserBoundaryTimes, boundaryTimes...) { @@ -731,7 +734,7 @@ func TestDatetimeOutOfRange(t *testing.T) { func TestDatetimeReplace(t *testing.T) { skipIfDatetimeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() tm, err := time.Parse(time.RFC3339, "2007-01-02T15:04:05Z") @@ -896,7 +899,7 @@ func (c *Tuple2) DecodeMsgpack(d *msgpack.Decoder) error { func TestCustomEncodeDecodeTuple1(t *testing.T) { skipIfDatetimeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() tm1, _ := time.Parse(time.RFC3339, "2010-05-24T17:51:56.000000009Z") @@ -966,7 +969,7 @@ func TestCustomEncodeDecodeTuple1(t *testing.T) { func TestCustomDecodeFunction(t *testing.T) { skipIfDatetimeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Call function 'call_datetime_testdata' returning a custom tuples. @@ -1010,7 +1013,7 @@ func TestCustomDecodeFunction(t *testing.T) { func TestCustomEncodeDecodeTuple5(t *testing.T) { skipIfDatetimeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() tm := time.Unix(500, 1000).In(time.FixedZone(NoTimezone, 0)) @@ -1166,10 +1169,9 @@ func runTestMain(m *testing.M) int { } instance, err := test_helpers.StartTarantool(test_helpers.StartOpts{ + Dialer: dialer, InitScript: "config.lua", Listen: server, - User: opts.User, - Pass: opts.Pass, WaitStart: 100 * time.Millisecond, ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, diff --git a/datetime/example_test.go b/datetime/example_test.go index 954f43548..72d3448c2 100644 --- a/datetime/example_test.go +++ b/datetime/example_test.go @@ -20,13 +20,15 @@ import ( // Example demonstrates how to use tuples with datetime. To enable support of // datetime import tarantool/datetime package. func Example() { - opts := tarantool.Opts{ - User: "test", - Pass: "test", + dialer := tarantool.NetDialer{ + Address: "127.0.0.1:3013", + User: "test", + Password: "test", } + opts := tarantool.Opts{} ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() - conn, err := tarantool.Connect(ctx, "127.0.0.1:3013", opts) + conn, err := tarantool.Connect(ctx, dialer, opts) if err != nil { fmt.Printf("Error in connect is %v", err) return diff --git a/datetime/interval_test.go b/datetime/interval_test.go index ae42e92c7..4e3cf5ab1 100644 --- a/datetime/interval_test.go +++ b/datetime/interval_test.go @@ -105,7 +105,7 @@ func TestIntervalSub(t *testing.T) { func TestIntervalTarantoolEncoding(t *testing.T) { skipIfDatetimeUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() cases := []Interval{ diff --git a/decimal/decimal_test.go b/decimal/decimal_test.go index 57c70d1d7..14ce05b2a 100644 --- a/decimal/decimal_test.go +++ b/decimal/decimal_test.go @@ -20,10 +20,13 @@ import ( var isDecimalSupported = false var server = "127.0.0.1:3013" +var dialer = NetDialer{ + Address: server, + User: "test", + Password: "test", +} var opts = Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", } func skipIfDecimalUnsupported(t *testing.T) { @@ -526,7 +529,7 @@ func BenchmarkDecodeStringFromBCD(b *testing.B) { func TestSelect(t *testing.T) { skipIfDecimalUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() number, err := decimal.NewFromString("-12.34") @@ -572,7 +575,7 @@ func TestSelect(t *testing.T) { func TestUnmarshal_from_decimal_new(t *testing.T) { skipIfDecimalUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() samples := correctnessSamples @@ -627,7 +630,7 @@ func assertInsert(t *testing.T, conn *Connection, numString string) { func TestInsert(t *testing.T) { skipIfDecimalUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() samples := correctnessSamples @@ -642,7 +645,7 @@ func TestInsert(t *testing.T) { func TestReplace(t *testing.T) { skipIfDecimalUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() number, err := decimal.NewFromString("-12.34") @@ -695,10 +698,9 @@ func runTestMain(m *testing.M) int { } instance, err := test_helpers.StartTarantool(test_helpers.StartOpts{ + Dialer: dialer, InitScript: "config.lua", Listen: server, - User: opts.User, - Pass: opts.Pass, WaitStart: 100 * time.Millisecond, ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, diff --git a/decimal/example_test.go b/decimal/example_test.go index f1984283c..3f7d4de06 100644 --- a/decimal/example_test.go +++ b/decimal/example_test.go @@ -22,13 +22,16 @@ import ( // import tarantool/decimal submodule. func Example() { server := "127.0.0.1:3013" + dialer := tarantool.NetDialer{ + Address: server, + User: "test", + Password: "test", + } opts := tarantool.Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", } ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - client, err := tarantool.Connect(ctx, server, opts) + client, err := tarantool.Connect(ctx, dialer, opts) cancel() if err != nil { log.Fatalf("Failed to connect: %s", err.Error()) diff --git a/dial.go b/dial.go index 5b17c0534..6b75adafa 100644 --- a/dial.go +++ b/dial.go @@ -15,10 +15,7 @@ import ( "github.com/vmihailenco/msgpack/v5" ) -const ( - dialTransportNone = "" - dialTransportSsl = "ssl" -) +const bufSize = 128 * 1024 // Greeting is a message sent by Tarantool on connect. type Greeting struct { @@ -45,47 +42,31 @@ type Conn interface { // Any blocked Read or Flush operations will be unblocked and return // errors. Close() error - // LocalAddr returns the local network address, if known. - LocalAddr() net.Addr - // RemoteAddr returns the remote network address, if known. - RemoteAddr() net.Addr // Greeting returns server greeting. Greeting() Greeting // ProtocolInfo returns server protocol info. ProtocolInfo() ProtocolInfo + // Addr returns the connection address. + Addr() net.Addr } // DialOpts is a way to configure a Dial method to create a new Conn. type DialOpts struct { // IoTimeout is a timeout per a network read/write. IoTimeout time.Duration - // Transport is a connect transport type. - Transport string - // Ssl configures "ssl" transport. - Ssl SslOpts - // RequiredProtocol contains minimal protocol version and - // list of protocol features that should be supported by - // Tarantool server. By default there are no restrictions. - RequiredProtocol ProtocolInfo - // Auth is an authentication method. - Auth Auth - // Username for logging in to Tarantool. - User string - // User password for logging in to Tarantool. - Password string } // Dialer is the interface that wraps a method to connect to a Tarantool // instance. The main idea is to provide a ready-to-work connection with // basic preparation, successful authorization and additional checks. // -// You can provide your own implementation to Connect() call via Opts.Dialer if -// some functionality is not implemented in the connector. See TtDialer.Dial() +// You can provide your own implementation to Connect() call if +// some functionality is not implemented in the connector. See NetDialer.Dial() // implementation as example. type Dialer interface { // Dial connects to a Tarantool instance to the address with specified // options. - Dial(ctx context.Context, address string, opts DialOpts) (Conn, error) + Dial(ctx context.Context, opts DialOpts) (Conn, error) } type tntConn struct { @@ -96,61 +77,186 @@ type tntConn struct { protocol ProtocolInfo } -// TtDialer is a default implementation of the Dialer interface which is -// used by the connector. -type TtDialer struct { +// rawDial does basic dial operations: +// reads greeting, identifies a protocol and validates it. +func rawDial(conn *tntConn, requiredProto ProtocolInfo) (string, error) { + version, salt, err := readGreeting(conn.reader) + if err != nil { + return "", fmt.Errorf("failed to read greeting: %w", err) + } + conn.greeting.Version = version + + if conn.protocol, err = identify(conn.writer, conn.reader); err != nil { + return "", fmt.Errorf("failed to identify: %w", err) + } + + if err = checkProtocolInfo(requiredProto, conn.protocol); err != nil { + return "", fmt.Errorf("invalid server protocol: %w", err) + } + return salt, err +} + +// NetDialer is a basic Dialer implementation. +type NetDialer struct { + // Address is an address to connect. + // It could be specified in following ways: + // + // - TCP connections (tcp://192.168.1.1:3013, tcp://my.host:3013, + // tcp:192.168.1.1:3013, tcp:my.host:3013, 192.168.1.1:3013, my.host:3013) + // + // - Unix socket, first '/' or '.' indicates Unix socket + // (unix:///abs/path/tnt.sock, unix:path/tnt.sock, /abs/path/tnt.sock, + // ./rel/path/tnt.sock, unix/:path/tnt.sock) + Address string + // Username for logging in to Tarantool. + User string + // User password for logging in to Tarantool. + Password string + // RequiredProtocol contains minimal protocol version and + // list of protocol features that should be supported by + // Tarantool server. By default, there are no restrictions. + RequiredProtocolInfo ProtocolInfo } -// Dial connects to a Tarantool instance to the address with specified -// options. -func (t TtDialer) Dial(ctx context.Context, address string, opts DialOpts) (Conn, error) { +// Dial makes NetDialer satisfy the Dialer interface. +func (d NetDialer) Dial(ctx context.Context, opts DialOpts) (Conn, error) { var err error conn := new(tntConn) - if conn.net, err = dial(ctx, address, opts); err != nil { + network, address := parseAddress(d.Address) + dialer := net.Dialer{} + conn.net, err = dialer.DialContext(ctx, network, address) + if err != nil { return nil, fmt.Errorf("failed to dial: %w", err) } dc := &deadlineIO{to: opts.IoTimeout, c: conn.net} - conn.reader = bufio.NewReaderSize(dc, 128*1024) - conn.writer = bufio.NewWriterSize(dc, 128*1024) + conn.reader = bufio.NewReaderSize(dc, bufSize) + conn.writer = bufio.NewWriterSize(dc, bufSize) - var version, salt string - if version, salt, err = readGreeting(conn.reader); err != nil { + salt, err := rawDial(conn, d.RequiredProtocolInfo) + if err != nil { conn.net.Close() - return nil, fmt.Errorf("failed to read greeting: %w", err) + return nil, err } - conn.greeting.Version = version - if conn.protocol, err = identify(conn.writer, conn.reader); err != nil { + if d.User == "" { + return conn, nil + } + + conn.protocol.Auth = ChapSha1Auth + if err = authenticate(conn, ChapSha1Auth, d.User, d.Password, salt); err != nil { conn.net.Close() - return nil, fmt.Errorf("failed to identify: %w", err) + return nil, fmt.Errorf("failed to authenticate: %w", err) + } + + return conn, nil +} + +// OpenSslDialer allows to use SSL transport for connection. +type OpenSslDialer struct { + // Address is an address to connect. + // It could be specified in following ways: + // + // - TCP connections (tcp://192.168.1.1:3013, tcp://my.host:3013, + // tcp:192.168.1.1:3013, tcp:my.host:3013, 192.168.1.1:3013, my.host:3013) + // + // - Unix socket, first '/' or '.' indicates Unix socket + // (unix:///abs/path/tnt.sock, unix:path/tnt.sock, /abs/path/tnt.sock, + // ./rel/path/tnt.sock, unix/:path/tnt.sock) + Address string + // Auth is an authentication method. + Auth Auth + // Username for logging in to Tarantool. + User string + // User password for logging in to Tarantool. + Password string + // RequiredProtocol contains minimal protocol version and + // list of protocol features that should be supported by + // Tarantool server. By default, there are no restrictions. + RequiredProtocolInfo ProtocolInfo + // SslKeyFile is a path to a private SSL key file. + SslKeyFile string + // SslCertFile is a path to an SSL certificate file. + SslCertFile string + // SslCaFile is a path to a trusted certificate authorities (CA) file. + SslCaFile string + // SslCiphers is a colon-separated (:) list of SSL cipher suites the connection + // can use. + // + // We don't provide a list of supported ciphers. This is what OpenSSL + // does. The only limitation is usage of TLSv1.2 (because other protocol + // versions don't seem to support the GOST cipher). To add additional + // ciphers (GOST cipher), you must configure OpenSSL. + // + // See also + // + // * https://www.openssl.org/docs/man1.1.1/man1/ciphers.html + SslCiphers string + // SslPassword is a password for decrypting the private SSL key file. + // The priority is as follows: try to decrypt with SslPassword, then + // try SslPasswordFile. + SslPassword string + // SslPasswordFile is a path to the list of passwords for decrypting + // the private SSL key file. The connection tries every line from the + // file as a password. + SslPasswordFile string +} + +// Dial makes OpenSslDialer satisfy the Dialer interface. +func (d OpenSslDialer) Dial(ctx context.Context, opts DialOpts) (Conn, error) { + var err error + conn := new(tntConn) + + network, address := parseAddress(d.Address) + conn.net, err = sslDialContext(ctx, network, address, sslOpts{ + KeyFile: d.SslKeyFile, + CertFile: d.SslCertFile, + CaFile: d.SslCaFile, + Ciphers: d.SslCiphers, + Password: d.SslPassword, + PasswordFile: d.SslPasswordFile, + }) + if err != nil { + return nil, fmt.Errorf("failed to dial: %w", err) } - if err = checkProtocolInfo(opts.RequiredProtocol, conn.protocol); err != nil { + dc := &deadlineIO{to: opts.IoTimeout, c: conn.net} + conn.reader = bufio.NewReaderSize(dc, bufSize) + conn.writer = bufio.NewWriterSize(dc, bufSize) + + salt, err := rawDial(conn, d.RequiredProtocolInfo) + if err != nil { conn.net.Close() - return nil, fmt.Errorf("invalid server protocol: %w", err) + return nil, err } - if opts.User != "" { - if opts.Auth == AutoAuth { - if conn.protocol.Auth != AutoAuth { - opts.Auth = conn.protocol.Auth - } else { - opts.Auth = ChapSha1Auth - } - } + if d.User == "" { + return conn, nil + } - err := authenticate(conn, opts, salt) - if err != nil { - conn.net.Close() - return nil, fmt.Errorf("failed to authenticate: %w", err) + if d.Auth == AutoAuth { + if conn.protocol.Auth != AutoAuth { + d.Auth = conn.protocol.Auth + } else { + d.Auth = ChapSha1Auth } } + conn.protocol.Auth = d.Auth + + if err = authenticate(conn, d.Auth, d.User, d.Password, salt); err != nil { + conn.net.Close() + return nil, fmt.Errorf("failed to authenticate: %w", err) + } return conn, nil } +// Addr makes tntConn satisfy the Conn interface. +func (c *tntConn) Addr() net.Addr { + return c.net.RemoteAddr() +} + // Read makes tntConn satisfy the Conn interface. func (c *tntConn) Read(p []byte) (int, error) { return c.reader.Read(p) @@ -177,16 +283,6 @@ func (c *tntConn) Close() error { return c.net.Close() } -// RemoteAddr makes tntConn satisfy the Conn interface. -func (c *tntConn) RemoteAddr() net.Addr { - return c.net.RemoteAddr() -} - -// LocalAddr makes tntConn satisfy the Conn interface. -func (c *tntConn) LocalAddr() net.Addr { - return c.net.LocalAddr() -} - // Greeting makes tntConn satisfy the Conn interface. func (c *tntConn) Greeting() Greeting { return c.greeting @@ -197,20 +293,6 @@ func (c *tntConn) ProtocolInfo() ProtocolInfo { return c.protocol } -// dial connects to a Tarantool instance. -func dial(ctx context.Context, address string, opts DialOpts) (net.Conn, error) { - network, address := parseAddress(address) - switch opts.Transport { - case dialTransportNone: - dialer := net.Dialer{} - return dialer.DialContext(ctx, network, address) - case dialTransportSsl: - return sslDialContext(ctx, network, address, opts.Ssl) - default: - return nil, fmt.Errorf("unsupported transport type: %s", opts.Transport) - } -} - // parseAddress split address into network and address parts. func parseAddress(address string) (string, string) { network := "tcp" @@ -316,29 +398,21 @@ func checkProtocolInfo(required ProtocolInfo, actual ProtocolInfo) error { } } -// authenticate authenticate for a connection. -func authenticate(c Conn, opts DialOpts, salt string) error { - auth := opts.Auth - user := opts.User - pass := opts.Password - +// authenticate authenticates for a connection. +func authenticate(c Conn, auth Auth, user string, pass string, salt string) error { var req Request var err error - switch opts.Auth { + switch auth { case ChapSha1Auth: req, err = newChapSha1AuthRequest(user, pass, salt) if err != nil { return err } case PapSha256Auth: - if opts.Transport != dialTransportSsl { - return errors.New("forbidden to use " + auth.String() + - " unless SSL is enabled for the connection") - } req = newPapSha256AuthRequest(user, pass) default: - return errors.New("unsupported method " + opts.Auth.String()) + return errors.New("unsupported method " + auth.String()) } if err = writeRequest(c, req); err != nil { diff --git a/dial_test.go b/dial_test.go index 209fc50cd..c8cf1c778 100644 --- a/dial_test.go +++ b/dial_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tarantool/go-iproto" + "github.com/tarantool/go-openssl" "github.com/tarantool/go-tarantool/v2" "github.com/tarantool/go-tarantool/v2/test_helpers" @@ -23,7 +24,7 @@ type mockErrorDialer struct { err error } -func (m mockErrorDialer) Dial(ctx context.Context, address string, +func (m mockErrorDialer) Dial(ctx context.Context, opts tarantool.DialOpts) (tarantool.Conn, error) { return nil, m.err } @@ -36,22 +37,18 @@ func TestDialer_Dial_error(t *testing.T) { ctx, cancel := test_helpers.GetConnectContext() defer cancel() - conn, err := tarantool.Connect(ctx, "any", tarantool.Opts{ - Dialer: dialer, - }) + conn, err := tarantool.Connect(ctx, dialer, tarantool.Opts{}) assert.Nil(t, conn) assert.ErrorContains(t, err, errMsg) } type mockPassedDialer struct { - ctx context.Context - address string - opts tarantool.DialOpts + ctx context.Context + opts tarantool.DialOpts } -func (m *mockPassedDialer) Dial(ctx context.Context, address string, +func (m *mockPassedDialer) Dial(ctx context.Context, opts tarantool.DialOpts) (tarantool.Conn, error) { - m.address = address m.opts = opts if ctx != m.ctx { return nil, errors.New("wrong context") @@ -60,53 +57,30 @@ func (m *mockPassedDialer) Dial(ctx context.Context, address string, } func TestDialer_Dial_passedOpts(t *testing.T) { - const addr = "127.0.0.1:8080" - opts := tarantool.DialOpts{ - IoTimeout: 2, - Transport: "any", - Ssl: tarantool.SslOpts{ - KeyFile: "a", - CertFile: "b", - CaFile: "c", - Ciphers: "d", - }, - RequiredProtocol: tarantool.ProtocolInfo{ - Auth: tarantool.ChapSha1Auth, - Version: 33, - Features: []iproto.Feature{ - iproto.IPROTO_FEATURE_ERROR_EXTENSION, - }, - }, - Auth: tarantool.ChapSha1Auth, - User: "user", - Password: "password", - } - dialer := &mockPassedDialer{} + ctx, cancel := test_helpers.GetConnectContext() defer cancel() - dialer.ctx = ctx - conn, err := tarantool.Connect(ctx, addr, tarantool.Opts{ - Dialer: dialer, - Timeout: opts.IoTimeout, - Transport: opts.Transport, - Ssl: opts.Ssl, - Auth: opts.Auth, - User: opts.User, - Pass: opts.Password, - RequiredProtocolInfo: opts.RequiredProtocol, + dialOpts := tarantool.DialOpts{ + IoTimeout: opts.Timeout, + } + + conn, err := tarantool.Connect(ctx, dialer, tarantool.Opts{ + Timeout: opts.Timeout, }) assert.Nil(t, conn) assert.NotNil(t, err) assert.NotEqual(t, err.Error(), "wrong context") - assert.Equal(t, addr, dialer.address) - assert.Equal(t, opts, dialer.opts) + assert.Equal(t, dialOpts, dialer.opts) } type mockIoConn struct { + addr net.Addr + // Addr calls count. + addrCnt int // Sends an event on Read()/Write()/Flush(). read, written chan struct{} // Read()/Write() buffers. @@ -117,12 +91,8 @@ type mockIoConn struct { readWgDelay, writeWgDelay int // Write()/Read()/Flush()/Close() calls count. writeCnt, readCnt, flushCnt, closeCnt int - // LocalAddr()/RemoteAddr() calls count. - localCnt, remoteCnt int // Greeting()/ProtocolInfo() calls count. greetingCnt, infoCnt int - // Values for LocalAddr()/RemoteAddr(). - local, remote net.Addr // Value for Greeting(). greeting tarantool.Greeting // Value for ProtocolInfo(). @@ -171,16 +141,6 @@ func (m *mockIoConn) Close() error { return nil } -func (m *mockIoConn) LocalAddr() net.Addr { - m.localCnt++ - return m.local -} - -func (m *mockIoConn) RemoteAddr() net.Addr { - m.remoteCnt++ - return m.remote -} - func (m *mockIoConn) Greeting() tarantool.Greeting { m.greetingCnt++ return m.greeting @@ -191,6 +151,11 @@ func (m *mockIoConn) ProtocolInfo() tarantool.ProtocolInfo { return m.info } +func (m *mockIoConn) Addr() net.Addr { + m.addrCnt++ + return m.addr +} + type mockIoDialer struct { init func(conn *mockIoConn) conn *mockIoConn @@ -203,8 +168,7 @@ func newMockIoConn() *mockIoConn { return conn } -func (m *mockIoDialer) Dial(ctx context.Context, address string, - opts tarantool.DialOpts) (tarantool.Conn, error) { +func (m *mockIoDialer) Dial(ctx context.Context, opts tarantool.DialOpts) (tarantool.Conn, error) { m.conn = newMockIoConn() if m.init != nil { m.init(m.conn) @@ -221,9 +185,8 @@ func dialIo(t *testing.T, } ctx, cancel := test_helpers.GetConnectContext() defer cancel() - conn, err := tarantool.Connect(ctx, "any", + conn, err := tarantool.Connect(ctx, &dialer, tarantool.Opts{ - Dialer: &dialer, Timeout: 1000 * time.Second, // Avoid pings. SkipSchema: true, }) @@ -244,7 +207,6 @@ func TestConn_Close(t *testing.T) { } type stubAddr struct { - net.Addr str string } @@ -252,25 +214,14 @@ func (a stubAddr) String() string { return a.str } -func TestConn_LocalAddr(t *testing.T) { - const addr = "any" - conn, dialer := dialIo(t, func(conn *mockIoConn) { - conn.local = stubAddr{str: addr} - }) - defer func() { - dialer.conn.readWg.Done() - dialer.conn.writeWg.Done() - conn.Close() - }() - - assert.Equal(t, addr, conn.LocalAddr()) - assert.Equal(t, 1, dialer.conn.localCnt) +func (a stubAddr) Network() string { + return "stub" } -func TestConn_RemoteAddr(t *testing.T) { +func TestConn_Addr(t *testing.T) { const addr = "any" conn, dialer := dialIo(t, func(conn *mockIoConn) { - conn.remote = stubAddr{str: addr} + conn.addr = stubAddr{str: addr} }) defer func() { dialer.conn.readWg.Done() @@ -278,8 +229,8 @@ func TestConn_RemoteAddr(t *testing.T) { conn.Close() }() - assert.Equal(t, addr, conn.RemoteAddr()) - assert.Equal(t, 1, dialer.conn.remoteCnt) + assert.Equal(t, addr, conn.Addr().String()) + assert.Equal(t, 1, dialer.conn.addrCnt) } func TestConn_Greeting(t *testing.T) { @@ -316,7 +267,7 @@ func TestConn_ProtocolInfo(t *testing.T) { conn.Close() }() - assert.Equal(t, info, conn.ServerProtocolInfo()) + assert.Equal(t, info, conn.ProtocolInfo()) assert.Equal(t, 1, dialer.conn.infoCnt) } @@ -359,13 +310,11 @@ func TestConn_ReadWrite(t *testing.T) { } func TestConn_ContextCancel(t *testing.T) { - const addr = "127.0.0.1:8080" - - dialer := tarantool.TtDialer{} + dialer := tarantool.NetDialer{Address: "127.0.0.1:8080"} ctx, cancel := context.WithCancel(context.Background()) cancel() - conn, err := dialer.Dial(ctx, addr, tarantool.DialOpts{}) + conn, err := dialer.Dial(ctx, tarantool.DialOpts{}) assert.Nil(t, conn) assert.NotNil(t, err) @@ -373,3 +322,460 @@ func TestConn_ContextCancel(t *testing.T) { fmt.Sprintf("unexpected error, expected to contain %s, got %v", "operation was canceled", err)) } + +func genSalt() [64]byte { + salt := [64]byte{} + for i := 0; i < 44; i++ { + salt[i] = 'a' + } + return salt +} + +var ( + testDialUser = "test" + testDialPass = "test" + testDialVersion = [64]byte{'t', 'e', 's', 't'} + + // Salt with end zeros. + testDialSalt = genSalt() + + idRequestExpected = []byte{ + 0xce, 0x00, 0x00, 0x00, 29, // Length. + 0x82, // Header map. + 0x00, 0x49, + 0x01, 0xce, 0x00, 0x00, 0x00, 0x00, + + 0x82, // Data map. + 0x54, + 0xcf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, // Version. + 0x55, + 0x97, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // Features. + } + + idResponseTyped = tarantool.ProtocolInfo{ + Version: 6, + Features: []iproto.Feature{iproto.Feature(1), iproto.Feature(21)}, + Auth: tarantool.ChapSha1Auth, + } + + idResponse = []byte{ + 0xce, 0x00, 0x00, 0x00, 37, // Length. + 0x83, // Header map. + 0x00, 0xce, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xce, 0x00, 0x00, 0x00, 0x00, + 0x05, 0xce, 0x00, 0x00, 0x00, 0x61, + + 0x83, // Data map. + 0x54, + 0x06, // Version. + 0x55, + 0x92, 0x01, 0x15, // Features. + 0x5b, + 0xa9, 'c', 'h', 'a', 'p', '-', 's', 'h', 'a', '1', + } + + idResponseNotSupported = []byte{ + 0xce, 0x00, 0x00, 0x00, 25, // Length. + 0x83, // Header map. + 0x00, 0xce, 0x00, 0x00, 0x80, 0x30, + 0x01, 0xce, 0x00, 0x00, 0x00, 0x00, + 0x05, 0xce, 0x00, 0x00, 0x00, 0x61, + 0x81, + 0x31, + 0xa3, 'e', 'r', 'r', + } + + authRequestExpectedChapSha1 = []byte{ + 0xce, 0x00, 0x00, 0x00, 57, // Length. + 0x82, // Header map. + 0x00, 0x07, + 0x01, 0xce, 0x00, 0x00, 0x00, 0x00, + + 0x82, // Data map. + 0xce, 0x00, 0x00, 0x00, 0x23, + 0xa4, 't', 'e', 's', 't', // Login. + 0xce, 0x00, 0x00, 0x00, 0x21, + 0x92, // Tuple. + 0xa9, 'c', 'h', 'a', 'p', '-', 's', 'h', 'a', '1', + + // Scramble. + 0xb4, 0x1b, 0xd4, 0x20, 0x45, 0x73, 0x22, + 0xcf, 0xab, 0x05, 0x03, 0xf3, 0x89, 0x4b, + 0xfe, 0xc7, 0x24, 0x5a, 0xe6, 0xe8, 0x31, + } + + authRequestExpectedPapSha256 = []byte{ + 0xce, 0x00, 0x00, 0x00, 0x2a, // Length. + 0x82, // Header map. + 0x00, 0x07, + 0x01, 0xce, 0x00, 0x00, 0x00, 0x00, + + 0x82, // Data map. + 0xce, 0x00, 0x00, 0x00, 0x23, + 0xa4, 't', 'e', 's', 't', // Login. + 0xce, 0x00, 0x00, 0x00, 0x21, + 0x92, // Tuple. + 0xaa, 'p', 'a', 'p', '-', 's', 'h', 'a', '2', '5', '6', + 0xa4, 't', 'e', 's', 't', + } + + okResponse = []byte{ + 0xce, 0x00, 0x00, 0x00, 19, // Length. + 0x83, // Header map. + 0x00, 0xce, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xce, 0x00, 0x00, 0x00, 0x00, + 0x05, 0xce, 0x00, 0x00, 0x00, 0x61, + } + + errResponse = []byte{0xce} +) + +type testDialOpts struct { + name string + wantErr bool + expectedErr string + expectedProtocolInfo tarantool.ProtocolInfo + + // These options configure the behavior of the server. + isErrGreeting bool + isErrId bool + isIdUnsupported bool + isPapSha256Auth bool + isErrAuth bool +} + +type dialServerActual struct { + IdRequest []byte + AuthRequest []byte +} + +func testDialAccept(opts testDialOpts, l net.Listener) chan dialServerActual { + ch := make(chan dialServerActual, 1) + + go func() { + client, err := l.Accept() + if err != nil { + return + } + defer client.Close() + if opts.isErrGreeting { + client.Write(errResponse) + return + } else { + // Write greeting. + client.Write(testDialVersion[:]) + client.Write(testDialSalt[:]) + } + + // Read Id request. + idRequestActual := make([]byte, len(idRequestExpected)) + client.Read(idRequestActual) + + // Make Id response. + if opts.isErrId { + client.Write(errResponse) + } else if opts.isIdUnsupported { + client.Write(idResponseNotSupported) + } else { + client.Write(idResponse) + } + + // Read Auth request. + authRequestExpected := authRequestExpectedChapSha1 + if opts.isPapSha256Auth { + authRequestExpected = authRequestExpectedPapSha256 + } + authRequestActual := make([]byte, len(authRequestExpected)) + client.Read(authRequestActual) + + // Make Auth response. + if opts.isErrAuth { + client.Write(errResponse) + } else { + client.Write(okResponse) + } + ch <- dialServerActual{ + IdRequest: idRequestActual, + AuthRequest: authRequestActual, + } + }() + + return ch +} + +func testDialer(t *testing.T, l net.Listener, dialer tarantool.Dialer, + opts testDialOpts) { + ctx, cancel := test_helpers.GetConnectContext() + defer cancel() + ch := testDialAccept(opts, l) + conn, err := dialer.Dial(ctx, tarantool.DialOpts{ + IoTimeout: time.Second * 2, + }) + if opts.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), opts.expectedErr) + return + } + require.NoError(t, err) + require.Equal(t, opts.expectedProtocolInfo, conn.ProtocolInfo()) + require.Equal(t, testDialVersion[:], []byte(conn.Greeting().Version)) + + actual := <-ch + require.Equal(t, idRequestExpected, actual.IdRequest) + + authRequestExpected := authRequestExpectedChapSha1 + if opts.isPapSha256Auth { + authRequestExpected = authRequestExpectedPapSha256 + } + require.Equal(t, authRequestExpected, actual.AuthRequest) + conn.Close() +} + +func TestNetDialer_Dial(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + defer l.Close() + dialer := tarantool.NetDialer{ + Address: l.Addr().String(), + User: testDialUser, + Password: testDialPass, + } + cases := []testDialOpts{ + { + name: "all is ok", + expectedProtocolInfo: idResponseTyped.Clone(), + }, + { + name: "id request unsupported", + // Dialer sets auth. + expectedProtocolInfo: tarantool.ProtocolInfo{Auth: tarantool.ChapSha1Auth}, + isIdUnsupported: true, + }, + { + name: "greeting response error", + wantErr: true, + expectedErr: "failed to read greeting", + isErrGreeting: true, + }, + { + name: "id response error", + wantErr: true, + expectedErr: "failed to identify", + isErrId: true, + }, + { + name: "auth response error", + wantErr: true, + expectedErr: "failed to authenticate", + isErrAuth: true, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + testDialer(t, l, dialer, tc) + }) + } +} + +func TestNetDialer_Dial_requirements(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + defer l.Close() + dialer := tarantool.NetDialer{ + Address: l.Addr().String(), + User: testDialUser, + Password: testDialPass, + RequiredProtocolInfo: tarantool.ProtocolInfo{ + Features: []iproto.Feature{42}, + }, + } + testDialAccept(testDialOpts{}, l) + ctx, cancel := test_helpers.GetConnectContext() + defer cancel() + conn, err := dialer.Dial(ctx, tarantool.DialOpts{}) + if err == nil { + conn.Close() + } + require.Error(t, err) + require.Contains(t, err.Error(), "invalid server protocol") +} + +func createSslListener(t *testing.T, opts tarantool.SslTestOpts) net.Listener { + ctx, err := tarantool.SslCreateContext(opts) + require.NoError(t, err) + l, err := openssl.Listen("tcp", "127.0.0.1:0", ctx.(*openssl.Ctx)) + require.NoError(t, err) + return l +} + +func TestOpenSslDialer_Dial_basic(t *testing.T) { + l := createSslListener(t, tarantool.SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + }) + + defer l.Close() + addr := l.Addr().String() + + dialer := tarantool.OpenSslDialer{ + Address: addr, + User: testDialUser, + Password: testDialPass, + } + + cases := []testDialOpts{ + { + name: "all is ok", + expectedProtocolInfo: idResponseTyped.Clone(), + }, + { + name: "id request unsupported", + // Dialer sets auth. + expectedProtocolInfo: tarantool.ProtocolInfo{Auth: tarantool.ChapSha1Auth}, + isIdUnsupported: true, + }, + { + name: "greeting response error", + wantErr: true, + expectedErr: "failed to read greeting", + isErrGreeting: true, + }, + { + name: "id response error", + wantErr: true, + expectedErr: "failed to identify", + isErrId: true, + }, + { + name: "auth response error", + wantErr: true, + expectedErr: "failed to authenticate", + isErrAuth: true, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + testDialer(t, l, dialer, tc) + }) + } +} + +func TestOpenSslDialer_Dial_requirements(t *testing.T) { + l := createSslListener(t, tarantool.SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + }) + + defer l.Close() + addr := l.Addr().String() + + dialer := tarantool.OpenSslDialer{ + Address: addr, + User: testDialUser, + Password: testDialPass, + RequiredProtocolInfo: tarantool.ProtocolInfo{ + Features: []iproto.Feature{42}, + }, + } + + testDialAccept(testDialOpts{}, l) + ctx, cancel := test_helpers.GetConnectContext() + defer cancel() + conn, err := dialer.Dial(ctx, tarantool.DialOpts{}) + if err == nil { + conn.Close() + } + require.Error(t, err) + require.Contains(t, err.Error(), "invalid server protocol") +} + +func TestOpenSslDialer_Dial_papSha256Auth(t *testing.T) { + l := createSslListener(t, tarantool.SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + }) + + defer l.Close() + addr := l.Addr().String() + + dialer := tarantool.OpenSslDialer{ + Address: addr, + User: testDialUser, + Password: testDialPass, + Auth: tarantool.PapSha256Auth, + } + + protocol := idResponseTyped.Clone() + protocol.Auth = tarantool.PapSha256Auth + + testDialer(t, l, dialer, testDialOpts{ + expectedProtocolInfo: protocol, + isPapSha256Auth: true, + }) +} + +func TestOpenSslDialer_Dial_opts(t *testing.T) { + for _, test := range sslTests { + t.Run(test.name, func(t *testing.T) { + l := createSslListener(t, test.serverOpts) + defer l.Close() + addr := l.Addr().String() + + dialer := tarantool.OpenSslDialer{ + Address: addr, + User: testDialUser, + Password: testDialPass, + SslKeyFile: test.clientOpts.KeyFile, + SslCertFile: test.clientOpts.CertFile, + SslCaFile: test.clientOpts.CaFile, + SslCiphers: test.clientOpts.Ciphers, + SslPassword: test.clientOpts.Password, + SslPasswordFile: test.clientOpts.PasswordFile, + } + testDialer(t, l, dialer, testDialOpts{ + wantErr: !test.ok, + expectedProtocolInfo: idResponseTyped.Clone(), + }) + }) + } +} + +func TestOpenSslDialer_Dial_ctx_cancel(t *testing.T) { + serverOpts := tarantool.SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", + } + clientOpts := tarantool.SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", + } + + l := createSslListener(t, serverOpts) + defer l.Close() + addr := l.Addr().String() + testDialAccept(testDialOpts{}, l) + + dialer := tarantool.OpenSslDialer{ + Address: addr, + User: testDialUser, + Password: testDialPass, + SslKeyFile: clientOpts.KeyFile, + SslCertFile: clientOpts.CertFile, + SslCaFile: clientOpts.CaFile, + SslCiphers: clientOpts.Ciphers, + SslPassword: clientOpts.Password, + SslPasswordFile: clientOpts.PasswordFile, + } + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + conn, err := dialer.Dial(ctx, tarantool.DialOpts{}) + if err == nil { + conn.Close() + } + require.Error(t, err) +} diff --git a/example_custom_unpacking_test.go b/example_custom_unpacking_test.go index 8fb243db0..316e0086f 100644 --- a/example_custom_unpacking_test.go +++ b/example_custom_unpacking_test.go @@ -77,14 +77,15 @@ func (c *Tuple2) DecodeMsgpack(d *msgpack.Decoder) error { // a custom packer/unpacker, but it will work slower. func Example_customUnpacking() { // Establish a connection. - server := "127.0.0.1:3013" - opts := tarantool.Opts{ - Timeout: 500 * time.Millisecond, - User: "test", - Pass: "test", + + dialer := tarantool.NetDialer{ + Address: "127.0.0.1:3013", + User: "test", + Password: "test", } + opts := tarantool.Opts{} ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - conn, err := tarantool.Connect(ctx, server, opts) + conn, err := tarantool.Connect(ctx, dialer, opts) cancel() if err != nil { log.Fatalf("Failed to connect: %s", err.Error()) diff --git a/example_test.go b/example_test.go index 8e9cbf3a7..00cd00467 100644 --- a/example_test.go +++ b/example_test.go @@ -20,10 +20,10 @@ type Tuple struct { Name string } -func exampleConnect(opts tarantool.Opts) *tarantool.Connection { +func exampleConnect(dialer tarantool.Dialer, opts tarantool.Opts) *tarantool.Connection { ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() - conn, err := tarantool.Connect(ctx, server, opts) + conn, err := tarantool.Connect(ctx, dialer, opts) if err != nil { panic("Connection is not established: " + err.Error()) } @@ -31,27 +31,25 @@ func exampleConnect(opts tarantool.Opts) *tarantool.Connection { } // Example demonstrates how to use SSL transport. -func ExampleSslOpts() { - var opts = tarantool.Opts{ - User: "test", - Pass: "test", - Transport: "ssl", - Ssl: tarantool.SslOpts{ - KeyFile: "testdata/localhost.key", - CertFile: "testdata/localhost.crt", - CaFile: "testdata/ca.crt", - }, +func ExampleOpenSslDialer() { + sslDialer := tarantool.OpenSslDialer{ + Address: "127.0.0.1:3013", + User: "test", + Password: "test", + SslKeyFile: "testdata/localhost.key", + SslCertFile: "testdata/localhost.crt", + SslCaFile: "testdata/ca.crt", } ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() - _, err := tarantool.Connect(ctx, "127.0.0.1:3013", opts) + _, err := tarantool.Connect(ctx, sslDialer, opts) if err != nil { panic("Connection is not established: " + err.Error()) } } func ExampleIntKey() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() const space = "test" @@ -73,7 +71,7 @@ func ExampleIntKey() { } func ExampleUintKey() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() const space = "test" @@ -95,7 +93,7 @@ func ExampleUintKey() { } func ExampleStringKey() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() const space = "teststring" @@ -120,7 +118,7 @@ func ExampleStringKey() { } func ExampleIntIntKey() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() const space = "testintint" @@ -146,7 +144,7 @@ func ExampleIntIntKey() { } func ExamplePingRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Ping a Tarantool instance to check connection. @@ -166,7 +164,7 @@ func ExamplePingRequest() { // of the request. For those purposes use context.WithTimeout() as // the root context. func ExamplePingRequest_Context() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() timeout := time.Nanosecond @@ -191,7 +189,7 @@ func ExamplePingRequest_Context() { } func ExampleSelectRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() for i := 1111; i <= 1112; i++ { @@ -232,7 +230,7 @@ func ExampleSelectRequest() { } func ExampleSelectRequest_spaceAndIndexNames() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewSelectRequest(spaceName) @@ -247,7 +245,7 @@ func ExampleSelectRequest_spaceAndIndexNames() { } func ExampleInsertRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Insert a new tuple { 31, 1 }. @@ -289,7 +287,7 @@ func ExampleInsertRequest() { } func ExampleInsertRequest_spaceAndIndexNames() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewInsertRequest(spaceName) @@ -303,7 +301,7 @@ func ExampleInsertRequest_spaceAndIndexNames() { } func ExampleDeleteRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Insert a new tuple { 35, 1 }. @@ -346,7 +344,7 @@ func ExampleDeleteRequest() { } func ExampleDeleteRequest_spaceAndIndexNames() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewDeleteRequest(spaceName) @@ -361,7 +359,7 @@ func ExampleDeleteRequest_spaceAndIndexNames() { } func ExampleReplaceRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Insert a new tuple { 13, 1 }. @@ -420,7 +418,7 @@ func ExampleReplaceRequest() { } func ExampleReplaceRequest_spaceAndIndexNames() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewReplaceRequest(spaceName) @@ -434,7 +432,7 @@ func ExampleReplaceRequest_spaceAndIndexNames() { } func ExampleUpdateRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() for i := 1111; i <= 1112; i++ { @@ -465,7 +463,7 @@ func ExampleUpdateRequest() { } func ExampleUpdateRequest_spaceAndIndexNames() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewUpdateRequest(spaceName) @@ -480,7 +478,7 @@ func ExampleUpdateRequest_spaceAndIndexNames() { } func ExampleUpsertRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() var req tarantool.Request @@ -521,7 +519,7 @@ func ExampleUpsertRequest() { } func ExampleUpsertRequest_spaceAndIndexNames() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewUpsertRequest(spaceName) @@ -535,7 +533,7 @@ func ExampleUpsertRequest_spaceAndIndexNames() { } func ExampleCallRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Call a function 'simple_concat' with arguments. @@ -554,7 +552,7 @@ func ExampleCallRequest() { } func ExampleEvalRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Run raw Lua code. @@ -582,7 +580,7 @@ func ExampleExecuteRequest() { return } - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewExecuteRequest( @@ -700,31 +698,11 @@ func ExampleExecuteRequest() { fmt.Println("SQL Info", resp.SQLInfo) } -func ExampleProtocolVersion() { - conn := exampleConnect(opts) - defer conn.Close() +func getTestTxnDialer() tarantool.Dialer { + txnDialer := dialer - clientProtocolInfo := conn.ClientProtocolInfo() - fmt.Println("Connector client protocol version:", clientProtocolInfo.Version) - for _, f := range clientProtocolInfo.Features { - fmt.Println("Connector client protocol feature:", f) - } - // Output: - // Connector client protocol version: 6 - // Connector client protocol feature: IPROTO_FEATURE_STREAMS - // Connector client protocol feature: IPROTO_FEATURE_TRANSACTIONS - // Connector client protocol feature: IPROTO_FEATURE_ERROR_EXTENSION - // Connector client protocol feature: IPROTO_FEATURE_WATCHERS - // Connector client protocol feature: IPROTO_FEATURE_PAGINATION - // Connector client protocol feature: IPROTO_FEATURE_SPACE_AND_INDEX_NAMES - // Connector client protocol feature: IPROTO_FEATURE_WATCH_ONCE -} - -func getTestTxnOpts() tarantool.Opts { - txnOpts := opts.Clone() - - // Assert that server supports expected protocol features - txnOpts.RequiredProtocolInfo = tarantool.ProtocolInfo{ + // Assert that server supports expected protocol features. + txnDialer.RequiredProtocolInfo = tarantool.ProtocolInfo{ Version: tarantool.ProtocolVersion(1), Features: []iproto.Feature{ iproto.IPROTO_FEATURE_STREAMS, @@ -732,7 +710,7 @@ func getTestTxnOpts() tarantool.Opts { }, } - return txnOpts + return txnDialer } func ExampleCommitRequest() { @@ -746,8 +724,8 @@ func ExampleCommitRequest() { return } - txnOpts := getTestTxnOpts() - conn := exampleConnect(txnOpts) + txnDialer := getTestTxnDialer() + conn := exampleConnect(txnDialer, opts) defer conn.Close() stream, _ := conn.NewStream() @@ -823,8 +801,8 @@ func ExampleRollbackRequest() { return } - txnOpts := getTestTxnOpts() - conn := exampleConnect(txnOpts) + txnDialer := getTestTxnDialer() + conn := exampleConnect(txnDialer, opts) defer conn.Close() stream, _ := conn.NewStream() @@ -900,8 +878,8 @@ func ExampleBeginRequest_TxnIsolation() { return } - txnOpts := getTestTxnOpts() - conn := exampleConnect(txnOpts) + txnDialer := getTestTxnDialer() + conn := exampleConnect(txnDialer, opts) defer conn.Close() stream, _ := conn.NewStream() @@ -969,7 +947,7 @@ func ExampleBeginRequest_TxnIsolation() { } func ExampleFuture_GetIterator() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() const timeout = 3 * time.Second @@ -1006,10 +984,14 @@ func ExampleConnect() { ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() - conn, err := tarantool.Connect(ctx, "127.0.0.1:3013", tarantool.Opts{ + dialer := tarantool.NetDialer{ + Address: server, + User: "test", + Password: "test", + } + + conn, err := tarantool.Connect(ctx, dialer, tarantool.Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", Concurrency: 32, }) if err != nil { @@ -1025,10 +1007,14 @@ func ExampleConnect() { } func ExampleConnect_reconnects() { + dialer := tarantool.NetDialer{ + Address: "127.0.0.1:3013", + User: "test", + Password: "test", + } + opts := tarantool.Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", Concurrency: 32, Reconnect: time.Second, MaxReconnects: 10, @@ -1039,7 +1025,7 @@ func ExampleConnect_reconnects() { for i := uint(0); i < opts.MaxReconnects; i++ { ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - conn, err = tarantool.Connect(ctx, "127.0.0.1:3013", opts) + conn, err = tarantool.Connect(ctx, dialer, opts) cancel() if err == nil { break @@ -1060,7 +1046,7 @@ func ExampleConnect_reconnects() { // Example demonstrates how to retrieve information with space schema. func ExampleSchema() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() schema, err := tarantool.GetSchema(conn) @@ -1085,7 +1071,7 @@ func ExampleSchema() { // Example demonstrates how to update the connection schema. func ExampleConnection_SetSchema() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Get the actual schema. @@ -1099,7 +1085,7 @@ func ExampleConnection_SetSchema() { // Example demonstrates how to retrieve information with space schema. func ExampleSpace() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Save Schema to a local variable to avoid races @@ -1148,7 +1134,7 @@ func ExampleSpace() { // ExampleConnection_Do demonstrates how to send a request and process // a response. func ExampleConnection_Do() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // It could be any request. @@ -1175,7 +1161,7 @@ func ExampleConnection_Do() { // ExampleConnection_Do_failure demonstrates how to send a request and process // failure. func ExampleConnection_Do_failure() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // It could be any request. @@ -1222,15 +1208,17 @@ func ExampleConnection_NewPrepared() { return } - server := "127.0.0.1:3013" + dialer := tarantool.NetDialer{ + Address: "127.0.0.1:3013", + User: "test", + Password: "test", + } opts := tarantool.Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", } ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() - conn, err := tarantool.Connect(ctx, server, opts) + conn, err := tarantool.Connect(ctx, dialer, opts) if err != nil { fmt.Printf("Failed to connect: %s", err.Error()) } @@ -1264,21 +1252,24 @@ func ExampleConnection_NewWatcher() { return } - server := "127.0.0.1:3013" + dialer := tarantool.NetDialer{ + Address: "127.0.0.1:3013", + User: "test", + Password: "test", + // You can require the feature explicitly. + RequiredProtocolInfo: tarantool.ProtocolInfo{ + Features: []iproto.Feature{iproto.IPROTO_FEATURE_WATCHERS}, + }, + } + opts := tarantool.Opts{ Timeout: 5 * time.Second, Reconnect: 5 * time.Second, MaxReconnects: 3, - User: "test", - Pass: "test", - // You need to require the feature to create a watcher. - RequiredProtocolInfo: tarantool.ProtocolInfo{ - Features: []iproto.Feature{iproto.IPROTO_FEATURE_WATCHERS}, - }, } ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() - conn, err := tarantool.Connect(ctx, server, opts) + conn, err := tarantool.Connect(ctx, dialer, opts) if err != nil { fmt.Printf("Failed to connect: %s\n", err) return @@ -1304,7 +1295,7 @@ func ExampleConnection_NewWatcher() { // ExampleConnection_CloseGraceful_force demonstrates how to force close // a connection with graceful close in progress after a while. func ExampleConnection_CloseGraceful_force() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) eval := `local fiber = require('fiber') local time = ... @@ -1347,7 +1338,7 @@ func ExampleWatchOnceRequest() { return } - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() conn.Do(tarantool.NewBroadcastRequest(key).Value(value)).Get() diff --git a/export_test.go b/export_test.go index 2c28472b9..e92ce9bd9 100644 --- a/export_test.go +++ b/export_test.go @@ -1,20 +1,22 @@ package tarantool import ( - "context" - "net" "time" "github.com/vmihailenco/msgpack/v5" ) -func SslDialContext(ctx context.Context, network, address string, - opts SslOpts) (connection net.Conn, err error) { - return sslDialContext(ctx, network, address, opts) +type SslTestOpts struct { + KeyFile string + CertFile string + CaFile string + Ciphers string + Password string + PasswordFile string } -func SslCreateContext(opts SslOpts) (ctx interface{}, err error) { - return sslCreateContext(opts) +func SslCreateContext(opts SslTestOpts) (ctx interface{}, err error) { + return sslCreateContext(sslOpts(opts)) } // RefImplPingBody is reference implementation for filling of a ping diff --git a/pool/connection_pool.go b/pool/connection_pool.go index 6186683b3..c272310bd 100644 --- a/pool/connection_pool.go +++ b/pool/connection_pool.go @@ -23,7 +23,7 @@ import ( ) var ( - ErrEmptyAddrs = errors.New("addrs (first argument) should not be empty") + ErrEmptyDialers = errors.New("dialers (second argument) should not be empty") ErrWrongCheckTimeout = errors.New("wrong check timeout, must be greater than 0") ErrNoConnection = errors.New("no active connections") ErrTooManyArgs = errors.New("too many arguments") @@ -50,7 +50,7 @@ type ConnectionHandler interface { // The client code may cancel adding a connection to the pool. The client // need to return an error from the Discovered call for that. In this case // the pool will close connection and will try to reopen it later. - Discovered(conn *tarantool.Connection, role Role) error + Discovered(id string, conn *tarantool.Connection, role Role) error // Deactivated is called when a connection with a role has become // unavaileble to send requests. It happens if the connection is closed or // the connection role is switched. @@ -61,7 +61,7 @@ type ConnectionHandler interface { // Deactivated will not be called if a previous Discovered() call returns // an error. Because in this case, the connection does not become available // for sending requests. - Deactivated(conn *tarantool.Connection, role Role) error + Deactivated(id string, conn *tarantool.Connection, role Role) error } // Opts provides additional options (configurable via ConnectWithOpts). @@ -94,8 +94,8 @@ Main features: - Automatic master discovery by mode parameter. */ type ConnectionPool struct { - addrs map[string]*endpoint - addrsMutex sync.RWMutex + ends map[string]*endpoint + endsMutex sync.RWMutex connOpts tarantool.Opts opts Opts @@ -112,7 +112,8 @@ type ConnectionPool struct { var _ Pooler = (*ConnectionPool)(nil) type endpoint struct { - addr string + id string + dialer tarantool.Dialer notify chan tarantool.ConnEvent conn *tarantool.Connection role Role @@ -124,9 +125,10 @@ type endpoint struct { closeErr error } -func newEndpoint(addr string) *endpoint { +func newEndpoint(id string, dialer tarantool.Dialer) *endpoint { return &endpoint{ - addr: addr, + id: id, + dialer: dialer, notify: make(chan tarantool.ConnEvent, 100), conn: nil, role: UnknownRole, @@ -137,25 +139,25 @@ func newEndpoint(addr string) *endpoint { } } -// ConnectWithOpts creates pool for instances with addresses addrs -// with options opts. -func ConnectWithOpts(ctx context.Context, addrs []string, +// ConnectWithOpts creates pool for instances with specified dialers and options opts. +// Each dialer corresponds to a certain id by which they will be distinguished. +func ConnectWithOpts(ctx context.Context, dialers map[string]tarantool.Dialer, connOpts tarantool.Opts, opts Opts) (*ConnectionPool, error) { - if len(addrs) == 0 { - return nil, ErrEmptyAddrs + if len(dialers) == 0 { + return nil, ErrEmptyDialers } if opts.CheckTimeout <= 0 { return nil, ErrWrongCheckTimeout } - size := len(addrs) + size := len(dialers) rwPool := newRoundRobinStrategy(size) roPool := newRoundRobinStrategy(size) anyPool := newRoundRobinStrategy(size) connPool := &ConnectionPool{ - addrs: make(map[string]*endpoint), - connOpts: connOpts.Clone(), + ends: make(map[string]*endpoint), + connOpts: connOpts, opts: opts, state: unknownState, done: make(chan struct{}), @@ -164,11 +166,7 @@ func ConnectWithOpts(ctx context.Context, addrs []string, anyPool: anyPool, } - for _, addr := range addrs { - connPool.addrs[addr] = nil - } - - somebodyAlive, ctxCanceled := connPool.fillPools(ctx) + somebodyAlive, ctxCanceled := connPool.fillPools(ctx, dialers) if !somebodyAlive { connPool.state.set(closedState) if ctxCanceled { @@ -179,7 +177,7 @@ func ConnectWithOpts(ctx context.Context, addrs []string, connPool.state.set(connectedState) - for _, s := range connPool.addrs { + for _, s := range connPool.ends { endpointCtx, cancel := context.WithCancel(context.Background()) s.cancel = cancel go connPool.controller(endpointCtx, s) @@ -188,17 +186,18 @@ func ConnectWithOpts(ctx context.Context, addrs []string, return connPool, nil } -// ConnectWithOpts creates pool for instances with addresses addrs. +// Connect creates pool for instances with specified dialers. +// Each dialer corresponds to a certain id by which they will be distinguished. // // It is useless to set up tarantool.Opts.Reconnect value for a connection. // The connection pool has its own reconnection logic. See // Opts.CheckTimeout description. -func Connect(ctx context.Context, addrs []string, +func Connect(ctx context.Context, dialers map[string]tarantool.Dialer, connOpts tarantool.Opts) (*ConnectionPool, error) { opts := Opts{ CheckTimeout: 1 * time.Second, } - return ConnectWithOpts(ctx, addrs, connOpts, opts) + return ConnectWithOpts(ctx, dialers, connOpts, opts) } // ConnectedNow gets connected status of pool. @@ -235,32 +234,32 @@ func (p *ConnectionPool) ConfiguredTimeout(mode Mode) (time.Duration, error) { return conn.ConfiguredTimeout(), nil } -// Add adds a new endpoint with the address into the pool. This function +// Add adds a new endpoint with the id into the pool. This function // adds the endpoint only after successful connection. -func (p *ConnectionPool) Add(ctx context.Context, addr string) error { - e := newEndpoint(addr) +func (p *ConnectionPool) Add(ctx context.Context, id string, dialer tarantool.Dialer) error { + e := newEndpoint(id, dialer) - p.addrsMutex.Lock() + p.endsMutex.Lock() // Ensure that Close()/CloseGraceful() not in progress/done. if p.state.get() != connectedState { - p.addrsMutex.Unlock() + p.endsMutex.Unlock() return ErrClosed } - if _, ok := p.addrs[addr]; ok { - p.addrsMutex.Unlock() + if _, ok := p.ends[id]; ok { + p.endsMutex.Unlock() return ErrExists } endpointCtx, cancel := context.WithCancel(context.Background()) e.cancel = cancel - p.addrs[addr] = e - p.addrsMutex.Unlock() + p.ends[id] = e + p.endsMutex.Unlock() if err := p.tryConnect(ctx, e); err != nil { - p.addrsMutex.Lock() - delete(p.addrs, addr) - p.addrsMutex.Unlock() + p.endsMutex.Lock() + delete(p.ends, id) + p.endsMutex.Unlock() e.cancel() close(e.closed) return err @@ -270,13 +269,13 @@ func (p *ConnectionPool) Add(ctx context.Context, addr string) error { return nil } -// Remove removes an endpoint with the address from the pool. The call +// Remove removes an endpoint with the id from the pool. The call // closes an active connection gracefully. -func (p *ConnectionPool) Remove(addr string) error { - p.addrsMutex.Lock() - endpoint, ok := p.addrs[addr] +func (p *ConnectionPool) Remove(id string) error { + p.endsMutex.Lock() + endpoint, ok := p.ends[id] if !ok { - p.addrsMutex.Unlock() + p.endsMutex.Unlock() return errors.New("endpoint not exist") } @@ -290,20 +289,20 @@ func (p *ConnectionPool) Remove(addr string) error { close(endpoint.shutdown) } - delete(p.addrs, addr) - p.addrsMutex.Unlock() + delete(p.ends, id) + p.endsMutex.Unlock() <-endpoint.closed return nil } func (p *ConnectionPool) waitClose() []error { - p.addrsMutex.RLock() - endpoints := make([]*endpoint, 0, len(p.addrs)) - for _, e := range p.addrs { + p.endsMutex.RLock() + endpoints := make([]*endpoint, 0, len(p.ends)) + for _, e := range p.ends { endpoints = append(endpoints, e) } - p.addrsMutex.RUnlock() + p.endsMutex.RUnlock() errs := make([]error, 0, len(endpoints)) for _, e := range endpoints { @@ -319,12 +318,12 @@ func (p *ConnectionPool) waitClose() []error { func (p *ConnectionPool) Close() []error { if p.state.cas(connectedState, closedState) || p.state.cas(shutdownState, closedState) { - p.addrsMutex.RLock() - for _, s := range p.addrs { + p.endsMutex.RLock() + for _, s := range p.ends { s.cancel() close(s.close) } - p.addrsMutex.RUnlock() + p.endsMutex.RUnlock() } return p.waitClose() @@ -334,39 +333,23 @@ func (p *ConnectionPool) Close() []error { // for all requests to complete. func (p *ConnectionPool) CloseGraceful() []error { if p.state.cas(connectedState, shutdownState) { - p.addrsMutex.RLock() - for _, s := range p.addrs { + p.endsMutex.RLock() + for _, s := range p.ends { s.cancel() close(s.shutdown) } - p.addrsMutex.RUnlock() + p.endsMutex.RUnlock() } return p.waitClose() } -// GetAddrs gets addresses of connections in pool. -func (p *ConnectionPool) GetAddrs() []string { - p.addrsMutex.RLock() - defer p.addrsMutex.RUnlock() - - cpy := make([]string, len(p.addrs)) - - i := 0 - for addr := range p.addrs { - cpy[i] = addr - i++ - } +// GetInfo gets information of connections (connected status, ro/rw role). +func (p *ConnectionPool) GetInfo() map[string]ConnectionInfo { + info := make(map[string]ConnectionInfo) - return cpy -} - -// GetPoolInfo gets information of connections (connected status, ro/rw role). -func (p *ConnectionPool) GetPoolInfo() map[string]*ConnectionInfo { - info := make(map[string]*ConnectionInfo) - - p.addrsMutex.RLock() - defer p.addrsMutex.RUnlock() + p.endsMutex.RLock() + defer p.endsMutex.RUnlock() p.poolsMutex.RLock() defer p.poolsMutex.RUnlock() @@ -374,10 +357,12 @@ func (p *ConnectionPool) GetPoolInfo() map[string]*ConnectionInfo { return info } - for addr := range p.addrs { - conn, role := p.getConnectionFromPool(addr) + for id := range p.ends { + conn, role := p.getConnectionFromPool(id) if conn != nil { - info[addr] = &ConnectionInfo{ConnectedNow: conn.ConnectedNow(), ConnRole: role} + info[id] = ConnectionInfo{ConnectedNow: conn.ConnectedNow(), ConnRole: role} + } else { + info[id] = ConnectionInfo{ConnectedNow: false, ConnRole: UnknownRole} } } @@ -912,12 +897,10 @@ func (p *ConnectionPool) NewPrepared(expr string, userMode Mode) (*tarantool.Pre } // NewWatcher creates a new Watcher object for the connection pool. -// -// You need to require IPROTO_FEATURE_WATCHERS to use watchers, see examples -// for the function. +// A watcher could be created only for instances with the support. // // The behavior is same as if Connection.NewWatcher() called for each -// connection with a suitable role. +// connection with a suitable mode. // // Keep in mind that garbage collection of a watcher handle doesn’t lead to the // watcher’s destruction. In this case, the watcher remains registered. You @@ -932,24 +915,13 @@ func (p *ConnectionPool) NewPrepared(expr string, userMode Mode) (*tarantool.Pre // Since 1.10.0 func (p *ConnectionPool) NewWatcher(key string, callback tarantool.WatchCallback, mode Mode) (tarantool.Watcher, error) { - watchersRequired := false - for _, feature := range p.connOpts.RequiredProtocolInfo.Features { - if iproto.IPROTO_FEATURE_WATCHERS == feature { - watchersRequired = true - break - } - } - if !watchersRequired { - return nil, errors.New("the feature IPROTO_FEATURE_WATCHERS must " + - "be required by connection options to create a watcher") - } watcher := &poolWatcher{ container: &p.watcherContainer, mode: mode, key: key, callback: callback, - watchers: make(map[string]tarantool.Watcher), + watchers: make(map[*tarantool.Connection]tarantool.Watcher), unregistered: false, } @@ -964,6 +936,10 @@ func (p *ConnectionPool) NewWatcher(key string, conns := rr.GetConnections() for _, conn := range conns { + // Check that connection supports watchers. + if !isFeatureInSlice(iproto.IPROTO_FEATURE_WATCHERS, conn.ProtocolInfo().Features) { + continue + } if err := watcher.watch(conn); err != nil { conn.Close() } @@ -977,8 +953,16 @@ func (p *ConnectionPool) NewWatcher(key string, // the argument of type Mode is unused. func (p *ConnectionPool) Do(req tarantool.Request, userMode Mode) *tarantool.Future { if connectedReq, ok := req.(tarantool.ConnectedRequest); ok { - conn, _ := p.getConnectionFromPool(connectedReq.Conn().Addr()) - if conn == nil { + conns := p.anyPool.GetConnections() + isOurConnection := false + for _, conn := range conns { + // Compare raw pointers. + if conn == connectedReq.Conn() { + isOurConnection = true + break + } + } + if !isOurConnection { return newErrorFuture(ErrUnknownRequest) } return connectedReq.Conn().Do(req) @@ -1030,22 +1014,22 @@ func (p *ConnectionPool) getConnectionRole(conn *tarantool.Connection) (Role, er return UnknownRole, nil } -func (p *ConnectionPool) getConnectionFromPool(addr string) (*tarantool.Connection, Role) { - if conn := p.rwPool.GetConnByAddr(addr); conn != nil { +func (p *ConnectionPool) getConnectionFromPool(id string) (*tarantool.Connection, Role) { + if conn := p.rwPool.GetConnById(id); conn != nil { return conn, MasterRole } - if conn := p.roPool.GetConnByAddr(addr); conn != nil { + if conn := p.roPool.GetConnById(id); conn != nil { return conn, ReplicaRole } - return p.anyPool.GetConnByAddr(addr), UnknownRole + return p.anyPool.GetConnById(id), UnknownRole } -func (p *ConnectionPool) deleteConnection(addr string) { - if conn := p.anyPool.DeleteConnByAddr(addr); conn != nil { - if conn := p.rwPool.DeleteConnByAddr(addr); conn == nil { - p.roPool.DeleteConnByAddr(addr) +func (p *ConnectionPool) deleteConnection(id string) { + if conn := p.anyPool.DeleteConnById(id); conn != nil { + if conn := p.rwPool.DeleteConnById(id); conn == nil { + p.roPool.DeleteConnById(id) } // The internal connection deinitialization. p.watcherContainer.mutex.RLock() @@ -1058,109 +1042,108 @@ func (p *ConnectionPool) deleteConnection(addr string) { } } -func (p *ConnectionPool) addConnection(addr string, +func (p *ConnectionPool) addConnection(id string, conn *tarantool.Connection, role Role) error { // The internal connection initialization. p.watcherContainer.mutex.RLock() defer p.watcherContainer.mutex.RUnlock() - watched := []*poolWatcher{} - err := p.watcherContainer.foreach(func(watcher *poolWatcher) error { - watch := false - switch watcher.mode { - case RW: - watch = role == MasterRole - case RO: - watch = role == ReplicaRole - default: - watch = true - } - if watch { - if err := watcher.watch(conn); err != nil { - return err + if isFeatureInSlice(iproto.IPROTO_FEATURE_WATCHERS, conn.ProtocolInfo().Features) { + watched := []*poolWatcher{} + err := p.watcherContainer.foreach(func(watcher *poolWatcher) error { + watch := false + switch watcher.mode { + case RW: + watch = role == MasterRole + case RO: + watch = role == ReplicaRole + default: + watch = true } - watched = append(watched, watcher) - } - return nil - }) - if err != nil { - for _, watcher := range watched { - watcher.unwatch(conn) + if watch { + if err := watcher.watch(conn); err != nil { + return err + } + watched = append(watched, watcher) + } + return nil + }) + if err != nil { + for _, watcher := range watched { + watcher.unwatch(conn) + } + log.Printf("tarantool: failed initialize watchers for %s: %s", id, err) + return err } - log.Printf("tarantool: failed initialize watchers for %s: %s", addr, err) - return err } - p.anyPool.AddConn(addr, conn) + p.anyPool.AddConn(id, conn) switch role { case MasterRole: - p.rwPool.AddConn(addr, conn) + p.rwPool.AddConn(id, conn) case ReplicaRole: - p.roPool.AddConn(addr, conn) + p.roPool.AddConn(id, conn) } return nil } -func (p *ConnectionPool) handlerDiscovered(conn *tarantool.Connection, +func (p *ConnectionPool) handlerDiscovered(id string, conn *tarantool.Connection, role Role) bool { var err error if p.opts.ConnectionHandler != nil { - err = p.opts.ConnectionHandler.Discovered(conn, role) + err = p.opts.ConnectionHandler.Discovered(id, conn, role) } if err != nil { - addr := conn.Addr() - log.Printf("tarantool: storing connection to %s canceled: %s\n", addr, err) + log.Printf("tarantool: storing connection to %s canceled: %s\n", id, err) return false } return true } -func (p *ConnectionPool) handlerDeactivated(conn *tarantool.Connection, +func (p *ConnectionPool) handlerDeactivated(id string, conn *tarantool.Connection, role Role) { var err error if p.opts.ConnectionHandler != nil { - err = p.opts.ConnectionHandler.Deactivated(conn, role) + err = p.opts.ConnectionHandler.Deactivated(id, conn, role) } if err != nil { - addr := conn.Addr() - log.Printf("tarantool: deactivating connection to %s by user failed: %s\n", addr, err) + log.Printf("tarantool: deactivating connection to %s by user failed: %s\n", id, err) } } -func (p *ConnectionPool) deactivateConnection(addr string, - conn *tarantool.Connection, role Role) { - p.deleteConnection(addr) +func (p *ConnectionPool) deactivateConnection(id string, conn *tarantool.Connection, role Role) { + p.deleteConnection(id) conn.Close() - p.handlerDeactivated(conn, role) + p.handlerDeactivated(id, conn, role) } func (p *ConnectionPool) deactivateConnections() { - for address, endpoint := range p.addrs { + for id, endpoint := range p.ends { if endpoint != nil && endpoint.conn != nil { - p.deactivateConnection(address, endpoint.conn, endpoint.role) + p.deactivateConnection(id, endpoint.conn, endpoint.role) } } } func (p *ConnectionPool) processConnection(conn *tarantool.Connection, - addr string, end *endpoint) bool { + id string, end *endpoint) bool { role, err := p.getConnectionRole(conn) if err != nil { conn.Close() - log.Printf("tarantool: storing connection to %s failed: %s\n", addr, err) + log.Printf("tarantool: storing connection to %s failed: %s\n", id, err) return false } - if !p.handlerDiscovered(conn, role) { + if !p.handlerDiscovered(id, conn, role) { conn.Close() return false } - if p.addConnection(addr, conn, role) != nil { + if p.addConnection(id, conn, role) != nil { conn.Close() - p.handlerDeactivated(conn, role) + p.handlerDeactivated(id, conn, role) return false } @@ -1169,26 +1152,27 @@ func (p *ConnectionPool) processConnection(conn *tarantool.Connection, return true } -func (p *ConnectionPool) fillPools(ctx context.Context) (bool, bool) { +func (p *ConnectionPool) fillPools( + ctx context.Context, + dialers map[string]tarantool.Dialer) (bool, bool) { somebodyAlive := false ctxCanceled := false - // It is called before controller() goroutines so we don't expect + // It is called before controller() goroutines, so we don't expect // concurrency issues here. - for addr := range p.addrs { - end := newEndpoint(addr) - p.addrs[addr] = end - + for id, dialer := range dialers { + end := newEndpoint(id, dialer) + p.ends[id] = end connOpts := p.connOpts connOpts.Notify = end.notify - conn, err := tarantool.Connect(ctx, addr, connOpts) + conn, err := tarantool.Connect(ctx, dialer, connOpts) if err != nil { - log.Printf("tarantool: connect to %s failed: %s\n", addr, err.Error()) + log.Printf("tarantool: connect to %s failed: %s\n", id, err.Error()) select { case <-ctx.Done(): ctxCanceled = true - p.addrs[addr] = nil + p.ends[id] = nil log.Printf("tarantool: operation was canceled") p.deactivateConnections() @@ -1196,7 +1180,7 @@ func (p *ConnectionPool) fillPools(ctx context.Context) (bool, bool) { return false, ctxCanceled default: } - } else if p.processConnection(conn, addr, end) { + } else if p.processConnection(conn, id, end) { somebodyAlive = true } } @@ -1214,11 +1198,11 @@ func (p *ConnectionPool) updateConnection(e *endpoint) { if role, err := p.getConnectionRole(e.conn); err == nil { if e.role != role { - p.deleteConnection(e.addr) + p.deleteConnection(e.id) p.poolsMutex.Unlock() - p.handlerDeactivated(e.conn, e.role) - opened := p.handlerDiscovered(e.conn, role) + p.handlerDeactivated(e.id, e.conn, e.role) + opened := p.handlerDiscovered(e.id, e.conn, role) if !opened { e.conn.Close() e.conn = nil @@ -1231,17 +1215,17 @@ func (p *ConnectionPool) updateConnection(e *endpoint) { p.poolsMutex.Unlock() e.conn.Close() - p.handlerDeactivated(e.conn, role) + p.handlerDeactivated(e.id, e.conn, role) e.conn = nil e.role = UnknownRole return } - if p.addConnection(e.addr, e.conn, role) != nil { + if p.addConnection(e.id, e.conn, role) != nil { p.poolsMutex.Unlock() e.conn.Close() - p.handlerDeactivated(e.conn, role) + p.handlerDeactivated(e.id, e.conn, role) e.conn = nil e.role = UnknownRole return @@ -1251,11 +1235,11 @@ func (p *ConnectionPool) updateConnection(e *endpoint) { p.poolsMutex.Unlock() return } else { - p.deleteConnection(e.addr) + p.deleteConnection(e.id) p.poolsMutex.Unlock() e.conn.Close() - p.handlerDeactivated(e.conn, e.role) + p.handlerDeactivated(e.id, e.conn, e.role) e.conn = nil e.role = UnknownRole return @@ -1275,18 +1259,19 @@ func (p *ConnectionPool) tryConnect(ctx context.Context, e *endpoint) error { connOpts := p.connOpts connOpts.Notify = e.notify - conn, err := tarantool.Connect(ctx, e.addr, connOpts) + conn, err := tarantool.Connect(ctx, e.dialer, connOpts) if err == nil { role, err := p.getConnectionRole(conn) p.poolsMutex.Unlock() if err != nil { conn.Close() - log.Printf("tarantool: storing connection to %s failed: %s\n", e.addr, err) + log.Printf("tarantool: storing connection to %s failed: %s\n", + e.id, err) return err } - opened := p.handlerDiscovered(conn, role) + opened := p.handlerDiscovered(e.id, conn, role) if !opened { conn.Close() return errors.New("storing connection canceled") @@ -1296,14 +1281,14 @@ func (p *ConnectionPool) tryConnect(ctx context.Context, e *endpoint) error { if p.state.get() != connectedState { p.poolsMutex.Unlock() conn.Close() - p.handlerDeactivated(conn, role) + p.handlerDeactivated(e.id, conn, role) return ErrClosed } - if err = p.addConnection(e.addr, conn, role); err != nil { + if err = p.addConnection(e.id, conn, role); err != nil { p.poolsMutex.Unlock() conn.Close() - p.handlerDeactivated(conn, role) + p.handlerDeactivated(e.id, conn, role) return err } e.conn = conn @@ -1322,10 +1307,10 @@ func (p *ConnectionPool) reconnect(ctx context.Context, e *endpoint) { return } - p.deleteConnection(e.addr) + p.deleteConnection(e.id) p.poolsMutex.Unlock() - p.handlerDeactivated(e.conn, e.role) + p.handlerDeactivated(e.id, e.conn, e.role) e.conn = nil e.role = UnknownRole @@ -1358,12 +1343,12 @@ func (p *ConnectionPool) controller(ctx context.Context, e *endpoint) { case <-e.close: if e.conn != nil { p.poolsMutex.Lock() - p.deleteConnection(e.addr) + p.deleteConnection(e.id) p.poolsMutex.Unlock() if !shutdown { e.closeErr = e.conn.Close() - p.handlerDeactivated(e.conn, e.role) + p.handlerDeactivated(e.id, e.conn, e.role) close(e.closed) } else { // Force close the connection. @@ -1380,7 +1365,7 @@ func (p *ConnectionPool) controller(ctx context.Context, e *endpoint) { shutdown = true if e.conn != nil { p.poolsMutex.Lock() - p.deleteConnection(e.addr) + p.deleteConnection(e.id) p.poolsMutex.Unlock() // We need to catch s.close in the current goroutine, so @@ -1402,9 +1387,9 @@ func (p *ConnectionPool) controller(ctx context.Context, e *endpoint) { if e.conn != nil && e.conn.ClosedNow() { p.poolsMutex.Lock() if p.state.get() == connectedState { - p.deleteConnection(e.addr) + p.deleteConnection(e.id) p.poolsMutex.Unlock() - p.handlerDeactivated(e.conn, e.role) + p.handlerDeactivated(e.id, e.conn, e.role) e.conn = nil e.role = UnknownRole } else { @@ -1482,3 +1467,12 @@ func newErrorFuture(err error) *tarantool.Future { fut.SetError(err) return fut } + +func isFeatureInSlice(expected iproto.Feature, actualSlice []iproto.Feature) bool { + for _, actual := range actualSlice { + if expected == actual { + return true + } + } + return false +} diff --git a/pool/connection_pool_test.go b/pool/connection_pool_test.go index 8b19e671b..15e67b59d 100644 --- a/pool/connection_pool_test.go +++ b/pool/connection_pool_test.go @@ -13,21 +13,25 @@ import ( "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/tarantool/go-iproto" "github.com/vmihailenco/msgpack/v5" + "github.com/stretchr/testify/require" "github.com/tarantool/go-tarantool/v2" "github.com/tarantool/go-tarantool/v2/pool" "github.com/tarantool/go-tarantool/v2/test_helpers" ) +var user = "test" +var pass = "test" var spaceNo = uint32(520) var spaceName = "testPool" var indexNo = uint32(0) var ports = []string{"3013", "3014", "3015", "3016", "3017"} var host = "127.0.0.1" + +type DialersMap = map[string]tarantool.Dialer + var servers = []string{ strings.Join([]string{host, ports[0]}, ":"), strings.Join([]string{host, ports[1]}, ":"), @@ -36,10 +40,35 @@ var servers = []string{ strings.Join([]string{host, ports[4]}, ":"), } +func makeDialer(serv string) tarantool.Dialer { + return tarantool.NetDialer{ + Address: serv, + User: user, + Password: pass, + } +} + +func makeDialers(servers []string) []tarantool.Dialer { + dialers := make([]tarantool.Dialer, 0, len(servers)) + for _, serv := range servers { + dialers = append(dialers, makeDialer(serv)) + } + return dialers +} + +func makeDialersMap(servers []string) DialersMap { + dialersMap := DialersMap{} + for _, serv := range servers { + dialersMap[serv] = makeDialer(serv) + } + return dialersMap +} + +var dialers = makeDialers(servers) +var dialersMap = makeDialersMap(servers) + var connOpts = tarantool.Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", } var defaultCountRetry = 5 @@ -49,21 +78,24 @@ var instances []test_helpers.TarantoolInstance func TestConnError_IncorrectParams(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() - connPool, err := pool.Connect(ctx, []string{}, tarantool.Opts{}) + connPool, err := pool.Connect(ctx, DialersMap{}, tarantool.Opts{}) cancel() require.Nilf(t, connPool, "conn is not nil with incorrect param") require.NotNilf(t, err, "err is nil with incorrect params") - require.Equal(t, "addrs (first argument) should not be empty", err.Error()) + require.Equal(t, "dialers (second argument) should not be empty", err.Error()) ctx, cancel = test_helpers.GetPoolConnectContext() - connPool, err = pool.Connect(ctx, []string{"err1", "err2"}, connOpts) + connPool, err = pool.Connect(ctx, DialersMap{ + "err1": tarantool.NetDialer{Address: "err1"}, + "err2": tarantool.NetDialer{Address: "err2"}, + }, connOpts) cancel() require.Nilf(t, connPool, "conn is not nil with incorrect param") require.NotNilf(t, err, "err is nil with incorrect params") require.Equal(t, "no active connections", err.Error()) ctx, cancel = test_helpers.GetPoolConnectContext() - connPool, err = pool.ConnectWithOpts(ctx, servers, tarantool.Opts{}, pool.Opts{}) + connPool, err = pool.ConnectWithOpts(ctx, dialersMap, tarantool.Opts{}, pool.Opts{}) cancel() require.Nilf(t, connPool, "conn is not nil with incorrect param") require.NotNilf(t, err, "err is nil with incorrect params") @@ -71,10 +103,11 @@ func TestConnError_IncorrectParams(t *testing.T) { } func TestConnSuccessfully(t *testing.T) { - server := servers[0] + healthyServ := servers[0] + ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{"err", server}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap([]string{healthyServ, "err"}), connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -83,10 +116,10 @@ func TestConnSuccessfully(t *testing.T) { args := test_helpers.CheckStatusesArgs{ ConnPool: connPool, Mode: pool.ANY, - Servers: []string{server}, + Servers: []string{healthyServ}, ExpectedPoolStatus: true, ExpectedStatuses: map[string]bool{ - server: true, + healthyServ: true, }, } @@ -97,8 +130,6 @@ func TestConnSuccessfully(t *testing.T) { func TestConnErrorAfterCtxCancel(t *testing.T) { var connLongReconnectOpts = tarantool.Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", Reconnect: time.Second, MaxReconnects: 100, } @@ -109,7 +140,7 @@ func TestConnErrorAfterCtxCancel(t *testing.T) { var err error cancel() - connPool, err = pool.Connect(ctx, servers, connLongReconnectOpts) + connPool, err = pool.Connect(ctx, dialersMap, connLongReconnectOpts) if connPool != nil || err == nil { t.Fatalf("ConnectionPool was created after cancel") @@ -121,24 +152,25 @@ func TestConnErrorAfterCtxCancel(t *testing.T) { } type mockClosingDialer struct { - cnt int + addr string + cnt *int ctx context.Context ctxCancel context.CancelFunc } -func (m *mockClosingDialer) Dial(ctx context.Context, address string, +func (m *mockClosingDialer) Dial(ctx context.Context, opts tarantool.DialOpts) (tarantool.Conn, error) { + dialer := tarantool.NetDialer{ + Address: m.addr, + User: user, + Password: pass, + } + conn, err := dialer.Dial(m.ctx, tarantool.DialOpts{}) - dialer := tarantool.TtDialer{} - conn, err := dialer.Dial(m.ctx, address, tarantool.DialOpts{ - User: "test", - Password: "test", - }) - - if m.cnt == 0 { + if *m.cnt == 0 { m.ctxCancel() } - m.cnt++ + *m.cnt++ return conn, err } @@ -147,11 +179,18 @@ func TestContextCancelInProgress(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - dialer := &mockClosingDialer{0, ctx, cancel} + cnt := new(int) + poolDialers := DialersMap{} + for _, serv := range servers { + poolDialers[serv] = &mockClosingDialer{ + addr: serv, + cnt: cnt, + ctx: ctx, + ctxCancel: cancel, + } + } - connPool, err := pool.Connect(ctx, servers, tarantool.Opts{ - Dialer: dialer, - }) + connPool, err := pool.Connect(ctx, poolDialers, tarantool.Opts{}) require.NotNilf(t, err, "expected err after ctx cancel") assert.Truef(t, strings.Contains(err.Error(), "operation was canceled"), fmt.Sprintf("unexpected error, expected to contain %s, got %v", @@ -161,10 +200,19 @@ func TestContextCancelInProgress(t *testing.T) { func TestConnSuccessfullyDuplicates(t *testing.T) { server := servers[0] + + poolDialers := DialersMap{} + for i := 0; i < 4; i++ { + poolDialers[fmt.Sprintf("c%d", i)] = tarantool.NetDialer{ + Address: server, + User: user, + Password: pass, + } + } + ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{server, server, server, server}, - connOpts) + connPool, err := pool.Connect(ctx, poolDialers, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -173,18 +221,18 @@ func TestConnSuccessfullyDuplicates(t *testing.T) { args := test_helpers.CheckStatusesArgs{ ConnPool: connPool, Mode: pool.ANY, - Servers: []string{server}, + Servers: []string{"c0", "c1", "c2", "c3"}, ExpectedPoolStatus: true, ExpectedStatuses: map[string]bool{ - server: true, + "c0": true, + "c1": true, + "c2": true, + "c3": true, }, } err = test_helpers.CheckPoolStatuses(args) require.Nil(t, err) - - addrs := connPool.GetAddrs() - require.Equalf(t, []string{server}, addrs, "should be only one address") } func TestReconnect(t *testing.T) { @@ -192,7 +240,7 @@ func TestReconnect(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -233,14 +281,15 @@ func TestReconnect(t *testing.T) { } func TestDisconnect_withReconnect(t *testing.T) { - const serverId = 0 + serverId := 0 + server := servers[serverId] opts := connOpts opts.Reconnect = 10 * time.Second ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{servers[serverId]}, opts) + connPool, err := pool.Connect(ctx, makeDialersMap([]string{server}), opts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -286,7 +335,7 @@ func TestDisconnectAll(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{server1, server2}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap([]string{server1, server2}), connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -335,7 +384,7 @@ func TestDisconnectAll(t *testing.T) { func TestAdd(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{servers[0]}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap(servers[:1]), connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -343,7 +392,7 @@ func TestAdd(t *testing.T) { for _, server := range servers[1:] { ctx, cancel := test_helpers.GetConnectContext() - err = connPool.Add(ctx, server) + err = connPool.Add(ctx, server, dialersMap[server]) cancel() require.Nil(t, err) } @@ -368,8 +417,9 @@ func TestAdd(t *testing.T) { } func TestAdd_exist(t *testing.T) { + server := servers[0] ctx, cancel := test_helpers.GetPoolConnectContext() - connPool, err := pool.Connect(ctx, []string{servers[0]}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap([]string{server}), connOpts) cancel() require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -377,7 +427,7 @@ func TestAdd_exist(t *testing.T) { defer connPool.Close() ctx, cancel = test_helpers.GetConnectContext() - err = connPool.Add(ctx, servers[0]) + err = connPool.Add(ctx, server, makeDialer(server)) cancel() require.Equal(t, pool.ErrExists, err) @@ -387,7 +437,7 @@ func TestAdd_exist(t *testing.T) { Servers: servers, ExpectedPoolStatus: true, ExpectedStatuses: map[string]bool{ - servers[0]: true, + server: true, }, } @@ -397,16 +447,21 @@ func TestAdd_exist(t *testing.T) { } func TestAdd_unreachable(t *testing.T) { + server := servers[0] + ctx, cancel := test_helpers.GetPoolConnectContext() - connPool, err := pool.Connect(ctx, []string{servers[0]}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap([]string{server}), connOpts) cancel() require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") defer connPool.Close() + unhealthyServ := "127.0.0.2:6667" ctx, cancel = test_helpers.GetConnectContext() - err = connPool.Add(ctx, "127.0.0.2:6667") + err = connPool.Add(ctx, unhealthyServ, tarantool.NetDialer{ + Address: unhealthyServ, + }) cancel() // The OS-dependent error so we just check for existence. require.NotNil(t, err) @@ -417,7 +472,7 @@ func TestAdd_unreachable(t *testing.T) { Servers: servers, ExpectedPoolStatus: true, ExpectedStatuses: map[string]bool{ - servers[0]: true, + server: true, }, } @@ -427,22 +482,26 @@ func TestAdd_unreachable(t *testing.T) { } func TestAdd_afterClose(t *testing.T) { + server := servers[0] ctx, cancel := test_helpers.GetPoolConnectContext() - connPool, err := pool.Connect(ctx, []string{servers[0]}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap([]string{server}), connOpts) cancel() require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") connPool.Close() ctx, cancel = test_helpers.GetConnectContext() - err = connPool.Add(ctx, servers[0]) + err = connPool.Add(ctx, server, dialersMap[server]) cancel() assert.Equal(t, err, pool.ErrClosed) } func TestAdd_Close_concurrent(t *testing.T) { + serv0 := servers[0] + serv1 := servers[1] + ctx, cancel := test_helpers.GetPoolConnectContext() - connPool, err := pool.Connect(ctx, []string{servers[0]}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap([]string{serv0}), connOpts) cancel() require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -453,7 +512,7 @@ func TestAdd_Close_concurrent(t *testing.T) { defer wg.Done() ctx, cancel := test_helpers.GetConnectContext() - err = connPool.Add(ctx, servers[1]) + err = connPool.Add(ctx, serv1, makeDialer(serv1)) cancel() if err != nil { assert.Equal(t, pool.ErrClosed, err) @@ -466,8 +525,11 @@ func TestAdd_Close_concurrent(t *testing.T) { } func TestAdd_CloseGraceful_concurrent(t *testing.T) { + serv0 := servers[0] + serv1 := servers[1] + ctx, cancel := test_helpers.GetPoolConnectContext() - connPool, err := pool.Connect(ctx, []string{servers[0]}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap([]string{serv0}), connOpts) cancel() require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -478,7 +540,7 @@ func TestAdd_CloseGraceful_concurrent(t *testing.T) { defer wg.Done() ctx, cancel := test_helpers.GetConnectContext() - err = connPool.Add(ctx, servers[1]) + err = connPool.Add(ctx, serv1, makeDialer(serv1)) cancel() if err != nil { assert.Equal(t, pool.ErrClosed, err) @@ -493,7 +555,7 @@ func TestAdd_CloseGraceful_concurrent(t *testing.T) { func TestRemove(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -522,7 +584,7 @@ func TestRemove(t *testing.T) { func TestRemove_double(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{servers[0], servers[1]}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap(servers[:2]), connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -551,7 +613,7 @@ func TestRemove_double(t *testing.T) { func TestRemove_unknown(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{servers[0], servers[1]}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap(servers[:2]), connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -579,7 +641,7 @@ func TestRemove_unknown(t *testing.T) { func TestRemove_concurrent(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{servers[0], servers[1]}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap(servers[:2]), connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -628,7 +690,7 @@ func TestRemove_concurrent(t *testing.T) { func TestRemove_Close_concurrent(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{servers[0], servers[1]}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap(servers[:2]), connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -649,7 +711,7 @@ func TestRemove_Close_concurrent(t *testing.T) { func TestRemove_CloseGraceful_concurrent(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{servers[0], servers[1]}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap(servers[:2]), connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -673,7 +735,7 @@ func TestClose(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{server1, server2}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap([]string{server1, server2}), connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -715,7 +777,7 @@ func TestCloseGraceful(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{server1, server2}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap([]string{server1, server2}), connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -734,9 +796,11 @@ func TestCloseGraceful(t *testing.T) { require.Nil(t, err) eval := `local fiber = require('fiber') - local time = ... - fiber.sleep(time) + local time = ... + fiber.sleep(time) + ` + evalSleep := 3 // In seconds. req := tarantool.NewEvalRequest(eval).Args([]interface{}{evalSleep}) fut := connPool.Do(req, pool.ANY) @@ -779,7 +843,8 @@ func (h *testHandler) addErr(err error) { h.errs = append(h.errs, err) } -func (h *testHandler) Discovered(conn *tarantool.Connection, +func (h *testHandler) Discovered(id string, conn *tarantool.Connection, + role pool.Role) error { discovered := atomic.AddUint32(&h.discovered, 1) @@ -790,29 +855,29 @@ func (h *testHandler) Discovered(conn *tarantool.Connection, // discovered < 3 - initial open of connections // discovered >= 3 - update a connection after a role update - addr := conn.Addr() - if addr == servers[0] { + if id == servers[0] { if discovered < 3 && role != pool.MasterRole { - h.addErr(fmt.Errorf("unexpected init role %d for addr %s", role, addr)) + h.addErr(fmt.Errorf("unexpected init role %d for id %s", role, id)) } if discovered >= 3 && role != pool.ReplicaRole { - h.addErr(fmt.Errorf("unexpected updated role %d for addr %s", role, addr)) + h.addErr(fmt.Errorf("unexpected updated role %d for id %s", role, id)) } - } else if addr == servers[1] { + } else if id == servers[1] { if discovered >= 3 { - h.addErr(fmt.Errorf("unexpected discovery for addr %s", addr)) + h.addErr(fmt.Errorf("unexpected discovery for id %s", id)) } if role != pool.ReplicaRole { - h.addErr(fmt.Errorf("unexpected role %d for addr %s", role, addr)) + h.addErr(fmt.Errorf("unexpected role %d for id %s", role, id)) } } else { - h.addErr(fmt.Errorf("unexpected discovered addr %s", addr)) + h.addErr(fmt.Errorf("unexpected discovered id %s", id)) } return nil } -func (h *testHandler) Deactivated(conn *tarantool.Connection, +func (h *testHandler) Deactivated(id string, conn *tarantool.Connection, + role pool.Role) error { deactivated := atomic.AddUint32(&h.deactivated, 1) @@ -821,22 +886,21 @@ func (h *testHandler) Deactivated(conn *tarantool.Connection, return nil } - addr := conn.Addr() - if deactivated == 1 && addr == servers[0] { + if deactivated == 1 && id == servers[0] { // A first close is a role update. if role != pool.MasterRole { - h.addErr(fmt.Errorf("unexpected removed role %d for addr %s", role, addr)) + h.addErr(fmt.Errorf("unexpected removed role %d for id %s", role, id)) } return nil } - if addr == servers[0] || addr == servers[1] { + if id == servers[0] || id == servers[1] { // Close. if role != pool.ReplicaRole { - h.addErr(fmt.Errorf("unexpected removed role %d for addr %s", role, addr)) + h.addErr(fmt.Errorf("unexpected removed role %d for id %s", role, id)) } } else { - h.addErr(fmt.Errorf("unexpected removed addr %s", addr)) + h.addErr(fmt.Errorf("unexpected removed id %s", id)) } return nil @@ -844,9 +908,10 @@ func (h *testHandler) Deactivated(conn *tarantool.Connection, func TestConnectionHandlerOpenUpdateClose(t *testing.T) { poolServers := []string{servers[0], servers[1]} + poolDialers := makeDialersMap(poolServers) roles := []bool{false, true} - err := test_helpers.SetClusterRO(poolServers, connOpts, roles) + err := test_helpers.SetClusterRO(makeDialers(poolServers), connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") h := &testHandler{} @@ -856,7 +921,7 @@ func TestConnectionHandlerOpenUpdateClose(t *testing.T) { } ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.ConnectWithOpts(ctx, poolServers, connOpts, poolOpts) + connPool, err := pool.ConnectWithOpts(ctx, poolDialers, connOpts, poolOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -910,13 +975,15 @@ type testAddErrorHandler struct { discovered, deactivated int } -func (h *testAddErrorHandler) Discovered(conn *tarantool.Connection, +func (h *testAddErrorHandler) Discovered(id string, conn *tarantool.Connection, + role pool.Role) error { h.discovered++ return fmt.Errorf("any error") } -func (h *testAddErrorHandler) Deactivated(conn *tarantool.Connection, +func (h *testAddErrorHandler) Deactivated(id string, conn *tarantool.Connection, + role pool.Role) error { h.deactivated++ return nil @@ -932,7 +999,7 @@ func TestConnectionHandlerOpenError(t *testing.T) { } ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.ConnectWithOpts(ctx, poolServers, connOpts, poolOpts) + connPool, err := pool.ConnectWithOpts(ctx, makeDialersMap(poolServers), connOpts, poolOpts) if err == nil { defer connPool.Close() } @@ -945,7 +1012,8 @@ type testUpdateErrorHandler struct { discovered, deactivated uint32 } -func (h *testUpdateErrorHandler) Discovered(conn *tarantool.Connection, +func (h *testUpdateErrorHandler) Discovered(id string, conn *tarantool.Connection, + role pool.Role) error { atomic.AddUint32(&h.discovered, 1) @@ -956,7 +1024,8 @@ func (h *testUpdateErrorHandler) Discovered(conn *tarantool.Connection, return nil } -func (h *testUpdateErrorHandler) Deactivated(conn *tarantool.Connection, +func (h *testUpdateErrorHandler) Deactivated(id string, conn *tarantool.Connection, + role pool.Role) error { atomic.AddUint32(&h.deactivated, 1) return nil @@ -964,9 +1033,10 @@ func (h *testUpdateErrorHandler) Deactivated(conn *tarantool.Connection, func TestConnectionHandlerUpdateError(t *testing.T) { poolServers := []string{servers[0], servers[1]} + poolDialers := makeDialersMap(poolServers) roles := []bool{false, false} - err := test_helpers.SetClusterRO(poolServers, connOpts, roles) + err := test_helpers.SetClusterRO(makeDialers(poolServers), connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") h := &testUpdateErrorHandler{} @@ -976,7 +1046,7 @@ func TestConnectionHandlerUpdateError(t *testing.T) { } ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.ConnectWithOpts(ctx, poolServers, connOpts, poolOpts) + connPool, err := pool.ConnectWithOpts(ctx, poolDialers, connOpts, poolOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") defer connPool.Close() @@ -1023,7 +1093,7 @@ func TestRequestOnClosed(t *testing.T) { ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, []string{server1, server2}, connOpts) + connPool, err := pool.Connect(ctx, makeDialersMap([]string{server1, server2}), connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1056,35 +1126,15 @@ func TestRequestOnClosed(t *testing.T) { require.Nilf(t, err, "failed to restart tarantool") } -func TestGetPoolInfo(t *testing.T) { - server1 := servers[0] - server2 := servers[1] - - srvs := []string{server1, server2} - expected := []string{server1, server2} - ctx, cancel := test_helpers.GetPoolConnectContext() - defer cancel() - connPool, err := pool.Connect(ctx, srvs, connOpts) - require.Nilf(t, err, "failed to connect") - require.NotNilf(t, connPool, "conn is nil after Connect") - - defer connPool.Close() - - srvs[0] = "x" - connPool.GetAddrs()[1] = "y" - - require.ElementsMatch(t, expected, connPool.GetAddrs()) -} - func TestCall(t *testing.T) { roles := []bool{false, true, false, false, true} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1138,12 +1188,12 @@ func TestCall(t *testing.T) { func TestCall16(t *testing.T) { roles := []bool{false, true, false, false, true} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1197,12 +1247,12 @@ func TestCall16(t *testing.T) { func TestCall17(t *testing.T) { roles := []bool{false, true, false, false, true} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1256,12 +1306,12 @@ func TestCall17(t *testing.T) { func TestEval(t *testing.T) { roles := []bool{false, true, false, false, true} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1336,12 +1386,12 @@ func TestExecute(t *testing.T) { roles := []bool{false, true, false, false, true} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1395,12 +1445,12 @@ func TestRoundRobinStrategy(t *testing.T) { serversNumber := len(servers) - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1474,12 +1524,12 @@ func TestRoundRobinStrategy_NoReplica(t *testing.T) { servers[4]: true, } - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1547,12 +1597,12 @@ func TestRoundRobinStrategy_NoMaster(t *testing.T) { servers[4]: true, } - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1632,12 +1682,12 @@ func TestUpdateInstancesRoles(t *testing.T) { serversNumber := len(servers) - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1711,7 +1761,7 @@ func TestUpdateInstancesRoles(t *testing.T) { servers[3]: true, } - err = test_helpers.SetClusterRO(servers, connOpts, roles) + err = test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") // ANY @@ -1778,12 +1828,12 @@ func TestUpdateInstancesRoles(t *testing.T) { func TestInsert(t *testing.T) { roles := []bool{true, true, false, true, true} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1809,7 +1859,7 @@ func TestInsert(t *testing.T) { // Connect to servers[2] to check if tuple // was inserted on RW instance - conn := test_helpers.ConnectWithValidation(t, servers[2], connOpts) + conn := test_helpers.ConnectWithValidation(t, makeDialer(servers[2]), connOpts) defer conn.Close() sel := tarantool.NewSelectRequest(spaceNo). @@ -1879,12 +1929,12 @@ func TestInsert(t *testing.T) { func TestDelete(t *testing.T) { roles := []bool{true, true, false, true, true} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1892,7 +1942,7 @@ func TestDelete(t *testing.T) { // Connect to servers[2] to check if tuple // was inserted on RW instance - conn := test_helpers.ConnectWithValidation(t, servers[2], connOpts) + conn := test_helpers.ConnectWithValidation(t, makeDialer(servers[2]), connOpts) defer conn.Close() ins := tarantool.NewInsertRequest(spaceNo).Tuple([]interface{}{"delete_key", "delete_value"}) @@ -1945,12 +1995,12 @@ func TestDelete(t *testing.T) { func TestUpsert(t *testing.T) { roles := []bool{true, true, false, true, true} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -1958,7 +2008,7 @@ func TestUpsert(t *testing.T) { // Connect to servers[2] to check if tuple // was inserted on RW instance - conn := test_helpers.ConnectWithValidation(t, servers[2], connOpts) + conn := test_helpers.ConnectWithValidation(t, makeDialer(servers[2]), connOpts) defer conn.Close() // Mode is `RW` by default, we have only one RW instance (servers[2]) @@ -2019,12 +2069,12 @@ func TestUpsert(t *testing.T) { func TestUpdate(t *testing.T) { roles := []bool{true, true, false, true, true} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -2032,7 +2082,7 @@ func TestUpdate(t *testing.T) { // Connect to servers[2] to check if tuple // was inserted on RW instance - conn := test_helpers.ConnectWithValidation(t, servers[2], connOpts) + conn := test_helpers.ConnectWithValidation(t, makeDialer(servers[2]), connOpts) defer conn.Close() ins := tarantool.NewInsertRequest(spaceNo). @@ -2111,12 +2161,12 @@ func TestUpdate(t *testing.T) { func TestReplace(t *testing.T) { roles := []bool{true, true, false, true, true} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -2124,7 +2174,7 @@ func TestReplace(t *testing.T) { // Connect to servers[2] to check if tuple // was inserted on RW instance - conn := test_helpers.ConnectWithValidation(t, servers[2], connOpts) + conn := test_helpers.ConnectWithValidation(t, makeDialer(servers[2]), connOpts) defer conn.Close() ins := tarantool.NewInsertRequest(spaceNo). @@ -2199,12 +2249,12 @@ func TestReplace(t *testing.T) { func TestSelect(t *testing.T) { roles := []bool{true, true, false, true, false} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -2222,13 +2272,13 @@ func TestSelect(t *testing.T) { rwKey := []interface{}{"rw_select_key"} anyKey := []interface{}{"any_select_key"} - err = test_helpers.InsertOnInstances(roServers, connOpts, spaceNo, roTpl) + err = test_helpers.InsertOnInstances(makeDialers(roServers), connOpts, spaceNo, roTpl) require.Nil(t, err) - err = test_helpers.InsertOnInstances(rwServers, connOpts, spaceNo, rwTpl) + err = test_helpers.InsertOnInstances(makeDialers(rwServers), connOpts, spaceNo, rwTpl) require.Nil(t, err) - err = test_helpers.InsertOnInstances(allServers, connOpts, spaceNo, anyTpl) + err = test_helpers.InsertOnInstances(makeDialers(allServers), connOpts, spaceNo, anyTpl) require.Nil(t, err) //default: ANY @@ -2321,12 +2371,12 @@ func TestSelect(t *testing.T) { func TestPing(t *testing.T) { roles := []bool{true, true, false, true, false} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -2361,12 +2411,12 @@ func TestPing(t *testing.T) { func TestDo(t *testing.T) { roles := []bool{true, true, false, true, false} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -2402,12 +2452,12 @@ func TestDo(t *testing.T) { func TestDo_concurrent(t *testing.T) { roles := []bool{true, true, false, true, false} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -2435,12 +2485,12 @@ func TestNewPrepared(t *testing.T) { roles := []bool{true, true, false, true, false} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -2450,10 +2500,6 @@ func TestNewPrepared(t *testing.T) { "SELECT NAME0, NAME1 FROM SQL_TEST WHERE NAME0=:id AND NAME1=:name;", pool.RO) require.Nilf(t, err, "fail to prepare statement: %v", err) - if connPool.GetPoolInfo()[stmt.Conn.Addr()].ConnRole != pool.ReplicaRole { - t.Errorf("wrong role for the statement's connection") - } - executeReq := tarantool.NewExecutePreparedRequest(stmt) unprepareReq := tarantool.NewUnprepareRequest(stmt) @@ -2505,12 +2551,12 @@ func TestDoWithStrangerConn(t *testing.T) { roles := []bool{true, true, false, true, false} - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") @@ -2536,12 +2582,12 @@ func TestStream_Commit(t *testing.T) { roles := []bool{true, true, false, true, true} - err = test_helpers.SetClusterRO(servers, connOpts, roles) + err = test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") defer connPool.Close() @@ -2568,7 +2614,7 @@ func TestStream_Commit(t *testing.T) { // Connect to servers[2] to check if tuple // was inserted outside of stream on RW instance // before transaction commit - conn := test_helpers.ConnectWithValidation(t, servers[2], connOpts) + conn := test_helpers.ConnectWithValidation(t, makeDialer(servers[2]), connOpts) defer conn.Close() // Select not related to the transaction @@ -2637,12 +2683,12 @@ func TestStream_Rollback(t *testing.T) { roles := []bool{true, true, false, true, true} - err = test_helpers.SetClusterRO(servers, connOpts, roles) + err = test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") defer connPool.Close() @@ -2668,7 +2714,7 @@ func TestStream_Rollback(t *testing.T) { // Connect to servers[2] to check if tuple // was not inserted outside of stream on RW instance - conn := test_helpers.ConnectWithValidation(t, servers[2], connOpts) + conn := test_helpers.ConnectWithValidation(t, makeDialer(servers[2]), connOpts) defer conn.Close() // Select not related to the transaction @@ -2738,12 +2784,12 @@ func TestStream_TxnIsolationLevel(t *testing.T) { roles := []bool{true, true, false, true, true} - err = test_helpers.SetClusterRO(servers, connOpts, roles) + err = test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") defer connPool.Close() @@ -2754,7 +2800,7 @@ func TestStream_TxnIsolationLevel(t *testing.T) { // Connect to servers[2] to check if tuple // was not inserted outside of stream on RW instance - conn := test_helpers.ConnectWithValidation(t, servers[2], connOpts) + conn := test_helpers.ConnectWithValidation(t, makeDialer(servers[2]), connOpts) defer conn.Close() for _, level := range txnIsolationLevels { @@ -2827,30 +2873,29 @@ func TestStream_TxnIsolationLevel(t *testing.T) { } } -func TestConnectionPool_NewWatcher_noWatchersFeature(t *testing.T) { - const key = "TestConnectionPool_NewWatcher_noWatchersFeature" +func TestConnectionPool_NewWatcher_no_watchers(t *testing.T) { + test_helpers.SkipIfWatchersSupported(t) - roles := []bool{true, false, false, true, true} - - opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []iproto.Feature{} - err := test_helpers.SetClusterRO(servers, opts, roles) - require.Nilf(t, err, "fail to set roles for cluster") + const key = "TestConnectionPool_NewWatcher_no_watchers" ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, opts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") - require.NotNilf(t, connPool, "conn is nil after Connect") + require.NotNilf(t, connPool, "conn is nill after Connect") defer connPool.Close() - watcher, err := connPool.NewWatcher(key, - func(event tarantool.WatchEvent) {}, pool.ANY) - require.Nilf(t, watcher, "watcher must not be created") - require.NotNilf(t, err, "an error is expected") - expected := "the feature IPROTO_FEATURE_WATCHERS must be required by " + - "connection options to create a watcher" - require.Equal(t, expected, err.Error()) + ch := make(chan struct{}) + connPool.NewWatcher(key, func(event tarantool.WatchEvent) { + close(ch) + }, pool.ANY) + + select { + case <-time.After(time.Second): + break + case <-ch: + t.Fatalf("watcher was created for connection that doesn't support it") + } } func TestConnectionPool_NewWatcher_modes(t *testing.T) { @@ -2860,16 +2905,12 @@ func TestConnectionPool_NewWatcher_modes(t *testing.T) { roles := []bool{true, false, false, true, true} - opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - err := test_helpers.SetClusterRO(servers, opts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, opts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") defer connPool.Close() @@ -2910,7 +2951,7 @@ func TestConnectionPool_NewWatcher_modes(t *testing.T) { select { case event := <-events: require.NotNil(t, event.Conn) - addr := event.Conn.Addr() + addr := event.Conn.Addr().String() if val, ok := testMap[addr]; ok { testMap[addr] = val + 1 } else { @@ -2941,11 +2982,7 @@ func TestConnectionPool_NewWatcher_update(t *testing.T) { const initCnt = 2 roles := []bool{true, false, false, true, true} - opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - err := test_helpers.SetClusterRO(servers, opts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") poolOpts := pool.Opts{ @@ -2953,7 +2990,7 @@ func TestConnectionPool_NewWatcher_update(t *testing.T) { } ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - pool, err := pool.ConnectWithOpts(ctx, servers, opts, poolOpts) + pool, err := pool.ConnectWithOpts(ctx, dialersMap, connOpts, poolOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, pool, "conn is nil after Connect") @@ -2976,7 +3013,7 @@ func TestConnectionPool_NewWatcher_update(t *testing.T) { select { case event := <-events: require.NotNil(t, event.Conn) - addr := event.Conn.Addr() + addr := event.Conn.Addr().String() if val, ok := testMap[addr]; ok { testMap[addr] = val + 1 } else { @@ -2992,7 +3029,7 @@ func TestConnectionPool_NewWatcher_update(t *testing.T) { for i, role := range roles { roles[i] = !role } - err = test_helpers.SetClusterRO(servers, connOpts, roles) + err = test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") // Wait for all updated events. @@ -3000,7 +3037,7 @@ func TestConnectionPool_NewWatcher_update(t *testing.T) { select { case event := <-events: require.NotNil(t, event.Conn) - addr := event.Conn.Addr() + addr := event.Conn.Addr().String() if val, ok := testMap[addr]; ok { testMap[addr] = val + 1 } else { @@ -3030,16 +3067,12 @@ func TestWatcher_Unregister(t *testing.T) { const expectedCnt = 2 roles := []bool{true, false, false, true, true} - opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - err := test_helpers.SetClusterRO(servers, opts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - pool, err := pool.Connect(ctx, servers, opts) + pool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, pool, "conn is nil after Connect") defer pool.Close() @@ -3091,16 +3124,12 @@ func TestConnectionPool_NewWatcher_concurrent(t *testing.T) { roles := []bool{true, false, false, true, true} - opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - err := test_helpers.SetClusterRO(servers, opts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, opts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") defer connPool.Close() @@ -3133,16 +3162,12 @@ func TestWatcher_Unregister_concurrent(t *testing.T) { roles := []bool{true, false, false, true, true} - opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - err := test_helpers.SetClusterRO(servers, opts, roles) + err := test_helpers.SetClusterRO(dialers, connOpts, roles) require.Nilf(t, err, "fail to set roles for cluster") ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, opts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) require.Nilf(t, err, "failed to connect") require.NotNilf(t, connPool, "conn is nil after Connect") defer connPool.Close() @@ -3178,18 +3203,27 @@ func runTestMain(m *testing.M) int { // Tarantool supports streams and interactive transactions since version 2.10.0 isStreamUnsupported, err := test_helpers.IsTarantoolVersionLess(2, 10, 0) if err != nil { - log.Fatalf("Could not check the Tarantool version") + log.Fatalf("Could not check the Tarantool version: %s", err) + } + + instsOpts := make([]test_helpers.StartOpts, 0, len(servers)) + for _, serv := range servers { + instsOpts = append(instsOpts, test_helpers.StartOpts{ + Listen: serv, + Dialer: tarantool.NetDialer{ + Address: serv, + User: user, + Password: pass, + }, + InitScript: initScript, + WaitStart: waitStart, + ConnectRetry: connectRetry, + RetryTimeout: retryTimeout, + MemtxUseMvccEngine: !isStreamUnsupported, + }) } - instances, err = test_helpers.StartTarantoolInstances(servers, nil, test_helpers.StartOpts{ - InitScript: initScript, - User: connOpts.User, - Pass: connOpts.Pass, - WaitStart: waitStart, - ConnectRetry: connectRetry, - RetryTimeout: retryTimeout, - MemtxUseMvccEngine: !isStreamUnsupported, - }) + instances, err = test_helpers.StartTarantoolInstances(instsOpts) if err != nil { log.Fatalf("Failed to prepare test tarantool: %s", err) diff --git a/pool/example_test.go b/pool/example_test.go index a38ae2831..c4a919a93 100644 --- a/pool/example_test.go +++ b/pool/example_test.go @@ -2,7 +2,6 @@ package pool_test import ( "fmt" - "strings" "time" "github.com/tarantool/go-iproto" @@ -22,14 +21,42 @@ type Tuple struct { var testRoles = []bool{true, true, false, true, true} -func examplePool(roles []bool, connOpts tarantool.Opts) (*pool.ConnectionPool, error) { - err := test_helpers.SetClusterRO(servers, connOpts, roles) +func examplePool(roles []bool, + connOpts tarantool.Opts) (*pool.ConnectionPool, error) { + err := test_helpers.SetClusterRO(dialers, connOpts, roles) if err != nil { return nil, fmt.Errorf("ConnectionPool is not established") } ctx, cancel := test_helpers.GetPoolConnectContext() defer cancel() - connPool, err := pool.Connect(ctx, servers, connOpts) + connPool, err := pool.Connect(ctx, dialersMap, connOpts) + if err != nil || connPool == nil { + return nil, fmt.Errorf("ConnectionPool is not established") + } + + return connPool, nil +} + +func exampleFeaturesPool(roles []bool, connOpts tarantool.Opts, + requiredProtocol tarantool.ProtocolInfo) (*pool.ConnectionPool, error) { + poolDialersMap := map[string]tarantool.Dialer{} + poolDialers := []tarantool.Dialer{} + for _, serv := range servers { + poolDialersMap[serv] = tarantool.NetDialer{ + Address: serv, + User: user, + Password: pass, + RequiredProtocolInfo: requiredProtocol, + } + poolDialers = append(poolDialers, poolDialersMap[serv]) + } + err := test_helpers.SetClusterRO(poolDialers, connOpts, roles) + if err != nil { + return nil, fmt.Errorf("ConnectionPool is not established") + } + ctx, cancel := test_helpers.GetPoolConnectContext() + defer cancel() + connPool, err := pool.Connect(ctx, poolDialersMap, connOpts) if err != nil || connPool == nil { return nil, fmt.Errorf("ConnectionPool is not established") } @@ -94,11 +121,6 @@ func ExampleConnectionPool_NewWatcher() { const key = "foo" const value = "bar" - opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - connPool, err := examplePool(testRoles, connOpts) if err != nil { fmt.Println(err) @@ -122,49 +144,15 @@ func ExampleConnectionPool_NewWatcher() { time.Sleep(time.Second) } -func ExampleConnectionPool_NewWatcher_noWatchersFeature() { - const key = "foo" - - opts := connOpts.Clone() - opts.RequiredProtocolInfo.Features = []iproto.Feature{} - - connPool, err := examplePool(testRoles, connOpts) - if err != nil { - fmt.Println(err) - } - defer connPool.Close() - - callback := func(event tarantool.WatchEvent) {} - watcher, err := connPool.NewWatcher(key, callback, pool.ANY) - fmt.Println(watcher) - if err != nil { - // Need to split the error message into two lines to pass - // golangci-lint. - str := err.Error() - fmt.Println(strings.Trim(str[:56], " ")) - fmt.Println(str[56:]) - } else { - fmt.Println(err) - } - // Output: - // - // the feature IPROTO_FEATURE_WATCHERS must be required by - // connection options to create a watcher -} - -func getTestTxnOpts() tarantool.Opts { - txnOpts := connOpts.Clone() - +func getTestTxnProtocol() tarantool.ProtocolInfo { // Assert that server supports expected protocol features - txnOpts.RequiredProtocolInfo = tarantool.ProtocolInfo{ + return tarantool.ProtocolInfo{ Version: tarantool.ProtocolVersion(1), Features: []iproto.Feature{ iproto.IPROTO_FEATURE_STREAMS, iproto.IPROTO_FEATURE_TRANSACTIONS, }, } - - return txnOpts } func ExampleCommitRequest() { @@ -178,8 +166,7 @@ func ExampleCommitRequest() { return } - txnOpts := getTestTxnOpts() - connPool, err := examplePool(testRoles, txnOpts) + connPool, err := exampleFeaturesPool(testRoles, connOpts, getTestTxnProtocol()) if err != nil { fmt.Println(err) return @@ -265,9 +252,8 @@ func ExampleRollbackRequest() { return } - txnOpts := getTestTxnOpts() // example pool has only one rw instance - connPool, err := examplePool(testRoles, txnOpts) + connPool, err := exampleFeaturesPool(testRoles, connOpts, getTestTxnProtocol()) if err != nil { fmt.Println(err) return @@ -352,9 +338,8 @@ func ExampleBeginRequest_TxnIsolation() { return } - txnOpts := getTestTxnOpts() // example pool has only one rw instance - connPool, err := examplePool(testRoles, txnOpts) + connPool, err := exampleFeaturesPool(testRoles, connOpts, getTestTxnProtocol()) if err != nil { fmt.Println(err) return diff --git a/pool/round_robin.go b/pool/round_robin.go index c3400d371..6e0b158f4 100644 --- a/pool/round_robin.go +++ b/pool/round_robin.go @@ -8,27 +8,27 @@ import ( ) type roundRobinStrategy struct { - conns []*tarantool.Connection - indexByAddr map[string]uint - mutex sync.RWMutex - size uint64 - current uint64 + conns []*tarantool.Connection + indexById map[string]uint + mutex sync.RWMutex + size uint64 + current uint64 } func newRoundRobinStrategy(size int) *roundRobinStrategy { return &roundRobinStrategy{ - conns: make([]*tarantool.Connection, 0, size), - indexByAddr: make(map[string]uint), - size: 0, - current: 0, + conns: make([]*tarantool.Connection, 0, size), + indexById: make(map[string]uint, size), + size: 0, + current: 0, } } -func (r *roundRobinStrategy) GetConnByAddr(addr string) *tarantool.Connection { +func (r *roundRobinStrategy) GetConnById(id string) *tarantool.Connection { r.mutex.RLock() defer r.mutex.RUnlock() - index, found := r.indexByAddr[addr] + index, found := r.indexById[id] if !found { return nil } @@ -36,7 +36,7 @@ func (r *roundRobinStrategy) GetConnByAddr(addr string) *tarantool.Connection { return r.conns[index] } -func (r *roundRobinStrategy) DeleteConnByAddr(addr string) *tarantool.Connection { +func (r *roundRobinStrategy) DeleteConnById(id string) *tarantool.Connection { r.mutex.Lock() defer r.mutex.Unlock() @@ -44,20 +44,20 @@ func (r *roundRobinStrategy) DeleteConnByAddr(addr string) *tarantool.Connection return nil } - index, found := r.indexByAddr[addr] + index, found := r.indexById[id] if !found { return nil } - delete(r.indexByAddr, addr) + delete(r.indexById, id) conn := r.conns[index] r.conns = append(r.conns[:index], r.conns[index+1:]...) r.size -= 1 - for k, v := range r.indexByAddr { + for k, v := range r.indexById { if v > index { - r.indexByAddr[k] = v - 1 + r.indexById[k] = v - 1 } } @@ -81,25 +81,27 @@ func (r *roundRobinStrategy) GetNextConnection() *tarantool.Connection { return r.conns[r.nextIndex()] } -func (r *roundRobinStrategy) GetConnections() []*tarantool.Connection { +func (r *roundRobinStrategy) GetConnections() map[string]*tarantool.Connection { r.mutex.RLock() defer r.mutex.RUnlock() - ret := make([]*tarantool.Connection, len(r.conns)) - copy(ret, r.conns) + conns := map[string]*tarantool.Connection{} + for id, index := range r.indexById { + conns[id] = r.conns[index] + } - return ret + return conns } -func (r *roundRobinStrategy) AddConn(addr string, conn *tarantool.Connection) { +func (r *roundRobinStrategy) AddConn(id string, conn *tarantool.Connection) { r.mutex.Lock() defer r.mutex.Unlock() - if idx, ok := r.indexByAddr[addr]; ok { + if idx, ok := r.indexById[id]; ok { r.conns[idx] = conn } else { r.conns = append(r.conns, conn) - r.indexByAddr[addr] = uint(r.size) + r.indexById[id] = uint(r.size) r.size += 1 } } diff --git a/pool/round_robin_test.go b/pool/round_robin_test.go index 4aacb1a26..6f133a799 100644 --- a/pool/round_robin_test.go +++ b/pool/round_robin_test.go @@ -22,7 +22,7 @@ func TestRoundRobinAddDelete(t *testing.T) { } for i, addr := range addrs { - if conn := rr.DeleteConnByAddr(addr); conn != conns[i] { + if conn := rr.DeleteConnById(addr); conn != conns[i] { t.Errorf("Unexpected connection on address %s", addr) } } @@ -40,13 +40,13 @@ func TestRoundRobinAddDuplicateDelete(t *testing.T) { rr.AddConn(validAddr1, conn1) rr.AddConn(validAddr1, conn2) - if rr.DeleteConnByAddr(validAddr1) != conn2 { + if rr.DeleteConnById(validAddr1) != conn2 { t.Errorf("Unexpected deleted connection") } if !rr.IsEmpty() { t.Errorf("RoundRobin does not empty") } - if rr.DeleteConnByAddr(validAddr1) != nil { + if rr.DeleteConnById(validAddr1) != nil { t.Errorf("Unexpected value after second deletion") } } @@ -79,11 +79,12 @@ func TestRoundRobinStrategy_GetConnections(t *testing.T) { rr.AddConn(addr, conns[i]) } - rr.GetConnections()[1] = conns[0] // GetConnections() returns a copy. + rr.GetConnections()[validAddr2] = conns[0] // GetConnections() returns a copy. rrConns := rr.GetConnections() - for i, expected := range conns { - if expected != rrConns[i] { - t.Errorf("Unexpected connection on %d call", i) + + for i, addr := range addrs { + if conns[i] != rrConns[addr] { + t.Errorf("Unexpected connection on %s addr", addr) } } } diff --git a/pool/watcher.go b/pool/watcher.go index 2d2b72e17..f7c08213e 100644 --- a/pool/watcher.go +++ b/pool/watcher.go @@ -76,7 +76,7 @@ type poolWatcher struct { key string callback tarantool.WatchCallback // watchers is a map connection -> connection watcher. - watchers map[string]tarantool.Watcher + watchers map[*tarantool.Connection]tarantool.Watcher // unregistered is true if the watcher already unregistered. unregistered bool // mutex for the pool watcher. @@ -101,18 +101,16 @@ func (w *poolWatcher) Unregister() { // watch adds a watcher for the connection. func (w *poolWatcher) watch(conn *tarantool.Connection) error { - addr := conn.Addr() - w.mutex.Lock() defer w.mutex.Unlock() if !w.unregistered { - if _, ok := w.watchers[addr]; ok { + if _, ok := w.watchers[conn]; ok { return nil } if watcher, err := conn.NewWatcher(w.key, w.callback); err == nil { - w.watchers[addr] = watcher + w.watchers[conn] = watcher return nil } else { return err @@ -123,15 +121,13 @@ func (w *poolWatcher) watch(conn *tarantool.Connection) error { // unwatch removes a watcher for the connection. func (w *poolWatcher) unwatch(conn *tarantool.Connection) { - addr := conn.Addr() - w.mutex.Lock() defer w.mutex.Unlock() if !w.unregistered { - if watcher, ok := w.watchers[addr]; ok { + if watcher, ok := w.watchers[conn]; ok { watcher.Unregister() - delete(w.watchers, addr) + delete(w.watchers, conn) } } } diff --git a/queue/example_connection_pool_test.go b/queue/example_connection_pool_test.go index a3bfaf0a4..b6e89d3f8 100644 --- a/queue/example_connection_pool_test.go +++ b/queue/example_connection_pool_test.go @@ -44,7 +44,7 @@ func NewQueueConnectionHandler(name string, cfg queue.Cfg) *QueueConnectionHandl // // NOTE: the Queue supports only a master-replica cluster configuration. It // does not support a master-master configuration. -func (h *QueueConnectionHandler) Discovered(conn *tarantool.Connection, +func (h *QueueConnectionHandler) Discovered(id string, conn *tarantool.Connection, role pool.Role) error { h.mutex.Lock() defer h.mutex.Unlock() @@ -106,14 +106,14 @@ func (h *QueueConnectionHandler) Discovered(conn *tarantool.Connection, return h.err } - fmt.Printf("Master %s is ready to work!\n", conn.Addr()) + fmt.Printf("Master %s is ready to work!\n", id) atomic.AddInt32(&h.masterCnt, 1) return nil } // Deactivated doesn't do anything useful for the example. -func (h *QueueConnectionHandler) Deactivated(conn *tarantool.Connection, +func (h *QueueConnectionHandler) Deactivated(id string, conn *tarantool.Connection, role pool.Role) error { if role == pool.MasterRole { atomic.AddInt32(&h.masterCnt, -1) @@ -152,14 +152,22 @@ func Example_connectionPool() { defer h.Close() // Create a ConnectionPool object. - servers := []string{ - "127.0.0.1:3014", - "127.0.0.1:3015", + poolServers := []string{"127.0.0.1:3014", "127.0.0.1:3015"} + poolDialers := []tarantool.Dialer{} + poolDialersMap := map[string]tarantool.Dialer{} + + for _, serv := range poolServers { + dialer := tarantool.NetDialer{ + Address: serv, + User: "test", + Password: "test", + } + poolDialers = append(poolDialers, dialer) + poolDialersMap[serv] = dialer } + connOpts := tarantool.Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", } poolOpts := pool.Opts{ CheckTimeout: 5 * time.Second, @@ -167,7 +175,7 @@ func Example_connectionPool() { } ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() - connPool, err := pool.ConnectWithOpts(ctx, servers, connOpts, poolOpts) + connPool, err := pool.ConnectWithOpts(ctx, poolDialersMap, connOpts, poolOpts) if err != nil { fmt.Printf("Unable to connect to the pool: %s", err) return @@ -199,7 +207,7 @@ func Example_connectionPool() { // Switch a master instance in the pool. roles := []bool{true, false} for { - err := test_helpers.SetClusterRO(servers, connOpts, roles) + err := test_helpers.SetClusterRO(poolDialers, connOpts, roles) if err == nil { break } diff --git a/queue/example_msgpack_test.go b/queue/example_msgpack_test.go index 6d3637417..cdd1a4e1c 100644 --- a/queue/example_msgpack_test.go +++ b/queue/example_msgpack_test.go @@ -49,15 +49,18 @@ func (c *dummyData) EncodeMsgpack(e *msgpack.Encoder) error { // cannot take the task out of the queue within the time corresponding to the // connection timeout. func Example_simpleQueueCustomMsgPack() { + dialer := tarantool.NetDialer{ + Address: "127.0.0.1:3013", + User: "test", + Password: "test", + } opts := tarantool.Opts{ Reconnect: time.Second, Timeout: 5 * time.Second, MaxReconnects: 5, - User: "test", - Pass: "test", } ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - conn, err := tarantool.Connect(ctx, "127.0.0.1:3013", opts) + conn, err := tarantool.Connect(ctx, dialer, opts) cancel() if err != nil { log.Fatalf("connection: %s", err) diff --git a/queue/example_test.go b/queue/example_test.go index e81acca40..1b411603a 100644 --- a/queue/example_test.go +++ b/queue/example_test.go @@ -28,13 +28,16 @@ func Example_simpleQueue() { } opts := tarantool.Opts{ Timeout: 2500 * time.Millisecond, - User: "test", - Pass: "test", + } + dialer := tarantool.NetDialer{ + Address: "127.0.0.1:3013", + User: "test", + Password: "test", } ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() - conn, err := tarantool.Connect(ctx, "127.0.0.1:3013", opts) + conn, err := tarantool.Connect(ctx, dialer, opts) if err != nil { fmt.Printf("error in prepare is %v", err) return diff --git a/queue/queue_test.go b/queue/queue_test.go index 575ce78a0..d23bd2c9c 100644 --- a/queue/queue_test.go +++ b/queue/queue_test.go @@ -15,18 +15,24 @@ import ( "github.com/tarantool/go-tarantool/v2/test_helpers" ) +const ( + user = "test" + pass = "test" +) + +var servers = []string{"127.0.0.1:3014", "127.0.0.1:3015"} var server = "127.0.0.1:3013" -var serversPool = []string{ - "127.0.0.1:3014", - "127.0.0.1:3015", -} var instances []test_helpers.TarantoolInstance +var dialer = NetDialer{ + Address: server, + User: user, + Password: pass, +} + var opts = Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", //Concurrency: 32, //RateLimit: 4*1024, } @@ -53,7 +59,7 @@ func dropQueue(t *testing.T, q queue.Queue) { /////////QUEUE///////// func TestFifoQueue(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -62,7 +68,7 @@ func TestFifoQueue(t *testing.T) { } func TestQueue_Cfg(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -76,7 +82,7 @@ func TestQueue_Cfg(t *testing.T) { } func TestQueue_Identify(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -99,7 +105,7 @@ func TestQueue_Identify(t *testing.T) { } func TestQueue_ReIdentify(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer func() { if conn != nil { conn.Close() @@ -152,7 +158,7 @@ func TestQueue_ReIdentify(t *testing.T) { conn.Close() conn = nil - conn = test_helpers.ConnectWithValidation(t, server, opts) + conn = test_helpers.ConnectWithValidation(t, dialer, opts) q = queue.New(conn, name) // Identify in another connection. @@ -182,7 +188,7 @@ func TestQueue_ReIdentify(t *testing.T) { } func TestQueue_State(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -199,7 +205,7 @@ func TestQueue_State(t *testing.T) { } func TestFifoQueue_GetExist_Statistic(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -229,7 +235,7 @@ func TestFifoQueue_GetExist_Statistic(t *testing.T) { } func TestFifoQueue_Put(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -249,7 +255,7 @@ func TestFifoQueue_Put(t *testing.T) { } func TestFifoQueue_Take(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -322,7 +328,7 @@ func (c *customData) EncodeMsgpack(e *msgpack.Encoder) error { } func TestFifoQueue_TakeTyped(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -385,7 +391,7 @@ func TestFifoQueue_TakeTyped(t *testing.T) { } func TestFifoQueue_Peek(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -417,7 +423,7 @@ func TestFifoQueue_Peek(t *testing.T) { } func TestFifoQueue_Bury_Kick(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -478,7 +484,7 @@ func TestFifoQueue_Bury_Kick(t *testing.T) { func TestFifoQueue_Delete(t *testing.T) { var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -530,7 +536,7 @@ func TestFifoQueue_Delete(t *testing.T) { } func TestFifoQueue_Release(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -590,7 +596,7 @@ func TestFifoQueue_Release(t *testing.T) { } func TestQueue_ReleaseAll(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -654,7 +660,7 @@ func TestQueue_ReleaseAll(t *testing.T) { } func TestTtlQueue(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -688,7 +694,7 @@ func TestTtlQueue(t *testing.T) { } func TestTtlQueue_Put(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_queue" @@ -737,7 +743,7 @@ func TestTtlQueue_Put(t *testing.T) { } func TestUtube_Put(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() name := "test_utube" @@ -810,7 +816,7 @@ func TestUtube_Put(t *testing.T) { } func TestTask_Touch(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() tests := []struct { @@ -896,10 +902,9 @@ func TestTask_Touch(t *testing.T) { // https://stackoverflow.com/questions/27629380/how-to-exit-a-go-program-honoring-deferred-calls func runTestMain(m *testing.M) int { inst, err := test_helpers.StartTarantool(test_helpers.StartOpts{ + Dialer: dialer, InitScript: "testdata/config.lua", Listen: server, - User: opts.User, - Pass: opts.Pass, WaitStart: 100 * time.Millisecond, ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, @@ -911,14 +916,22 @@ func runTestMain(m *testing.M) int { defer test_helpers.StopTarantoolWithCleanup(inst) - poolOpts := test_helpers.StartOpts{ - InitScript: "testdata/pool.lua", - User: opts.User, - Pass: opts.Pass, - WaitStart: 3 * time.Second, // replication_timeout * 3 - ConnectRetry: -1, + poolInstsOpts := make([]test_helpers.StartOpts, 0, len(servers)) + for _, serv := range servers { + poolInstsOpts = append(poolInstsOpts, test_helpers.StartOpts{ + Listen: serv, + Dialer: NetDialer{ + Address: serv, + User: user, + Password: pass, + }, + InitScript: "testdata/pool.lua", + WaitStart: 3 * time.Second, // replication_timeout * 3 + ConnectRetry: -1, + }) } - instances, err = test_helpers.StartTarantoolInstances(serversPool, nil, poolOpts) + + instances, err = test_helpers.StartTarantoolInstances(poolInstsOpts) if err != nil { log.Printf("Failed to prepare test tarantool pool: %s", err) @@ -933,11 +946,17 @@ func runTestMain(m *testing.M) int { roles := []bool{false, true} connOpts := Opts{ Timeout: 500 * time.Millisecond, - User: "test", - Pass: "test", + } + dialers := make([]Dialer, 0, len(servers)) + for _, serv := range servers { + dialers = append(dialers, NetDialer{ + Address: serv, + User: user, + Password: pass, + }) } - err = test_helpers.SetClusterRO(serversPool, connOpts, roles) + err = test_helpers.SetClusterRO(dialers, connOpts, roles) if err == nil { break } diff --git a/request.go b/request.go index ac0f7e858..8c4f4acd2 100644 --- a/request.go +++ b/request.go @@ -904,10 +904,28 @@ func (req authRequest) Ctx() context.Context { // Body fills an encoder with the auth request body. func (req authRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error { - return enc.Encode(map[uint32]interface{}{ - uint32(iproto.IPROTO_USER_NAME): req.user, - uint32(iproto.IPROTO_TUPLE): []interface{}{req.auth.String(), req.pass}, - }) + if err := enc.EncodeMapLen(2); err != nil { + return err + } + if err := enc.EncodeUint32(uint32(iproto.IPROTO_USER_NAME)); err != nil { + return err + } + if err := enc.EncodeString(req.user); err != nil { + return err + } + if err := enc.EncodeUint32(uint32(iproto.IPROTO_TUPLE)); err != nil { + return err + } + if err := enc.EncodeArrayLen(2); err != nil { + return err + } + if err := enc.EncodeString(req.auth.String()); err != nil { + return err + } + if err := enc.EncodeString(req.pass); err != nil { + return err + } + return nil } // PingRequest helps you to create an execute request object for execution diff --git a/settings/example_test.go b/settings/example_test.go index 29be33bfe..47f8e8c43 100644 --- a/settings/example_test.go +++ b/settings/example_test.go @@ -10,10 +10,20 @@ import ( "github.com/tarantool/go-tarantool/v2/test_helpers" ) -func example_connect(opts tarantool.Opts) *tarantool.Connection { +var exampleDialer = tarantool.NetDialer{ + Address: "127.0.0.1", + User: "test", + Password: "test", +} + +var exampleOpts = tarantool.Opts{ + Timeout: 5 * time.Second, +} + +func example_connect(dialer tarantool.Dialer, opts tarantool.Opts) *tarantool.Connection { ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() - conn, err := tarantool.Connect(ctx, server, opts) + conn, err := tarantool.Connect(ctx, dialer, opts) if err != nil { panic("Connection is not established: " + err.Error()) } @@ -25,7 +35,7 @@ func Example_sqlFullColumnNames() { var err error var isLess bool - conn := example_connect(opts) + conn := example_connect(exampleDialer, exampleOpts) defer conn.Close() // Tarantool supports session settings since version 2.3.1 diff --git a/settings/tarantool_test.go b/settings/tarantool_test.go index 2f7e01442..272693243 100644 --- a/settings/tarantool_test.go +++ b/settings/tarantool_test.go @@ -18,10 +18,13 @@ import ( var isSettingsSupported = false var server = "127.0.0.1:3013" +var dialer = tarantool.NetDialer{ + Address: server, + User: "test", + Password: "test", +} var opts = tarantool.Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", } func skipIfSettingsUnsupported(t *testing.T) { @@ -52,7 +55,7 @@ func TestErrorMarshalingEnabledSetting(t *testing.T) { var resp *tarantool.Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Disable receiving box.error as MP_EXT 3. @@ -101,7 +104,7 @@ func TestSQLDefaultEngineSetting(t *testing.T) { var resp *tarantool.Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Set default SQL "CREATE TABLE" engine to "vinyl". @@ -164,7 +167,7 @@ func TestSQLDeferForeignKeysSetting(t *testing.T) { var resp *tarantool.Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Create a parent space. @@ -237,7 +240,7 @@ func TestSQLFullColumnNamesSetting(t *testing.T) { var resp *tarantool.Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Create a space. @@ -299,7 +302,7 @@ func TestSQLFullMetadataSetting(t *testing.T) { var resp *tarantool.Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Create a space. @@ -360,7 +363,7 @@ func TestSQLParserDebugSetting(t *testing.T) { var resp *tarantool.Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Disable parser debug mode. @@ -398,7 +401,7 @@ func TestSQLRecursiveTriggersSetting(t *testing.T) { var resp *tarantool.Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Create a space. @@ -470,7 +473,7 @@ func TestSQLReverseUnorderedSelectsSetting(t *testing.T) { var resp *tarantool.Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Create a space. @@ -510,7 +513,7 @@ func TestSQLReverseUnorderedSelectsSetting(t *testing.T) { // Select multiple records. query := "SELECT * FROM seqscan data;" if isSeqScanOld, err := test_helpers.IsTarantoolVersionLess(3, 0, 0); err != nil { - t.Fatal("Could not check the Tarantool version") + t.Fatalf("Could not check the Tarantool version: %s", err) } else if isSeqScanOld { query = "SELECT * FROM data;" } @@ -549,7 +552,7 @@ func TestSQLSelectDebugSetting(t *testing.T) { var resp *tarantool.Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Disable select debug mode. @@ -586,7 +589,7 @@ func TestSQLVDBEDebugSetting(t *testing.T) { var resp *tarantool.Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Disable VDBE debug mode. @@ -623,7 +626,7 @@ func TestSessionSettings(t *testing.T) { var resp *tarantool.Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Set some settings values. @@ -668,10 +671,9 @@ func runTestMain(m *testing.M) int { isSettingsSupported = true inst, err := test_helpers.StartTarantool(test_helpers.StartOpts{ + Dialer: dialer, InitScript: "testdata/config.lua", Listen: server, - User: opts.User, - Pass: opts.Pass, WaitStart: 100 * time.Millisecond, ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, diff --git a/shutdown_test.go b/shutdown_test.go index cf4894344..80996e3a9 100644 --- a/shutdown_test.go +++ b/shutdown_test.go @@ -2,7 +2,6 @@ // +build linux darwin,!cgo // Use OS build flags since signals are system-dependent. - package tarantool_test import ( @@ -14,25 +13,26 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tarantool/go-iproto" . "github.com/tarantool/go-tarantool/v2" "github.com/tarantool/go-tarantool/v2/test_helpers" ) var shtdnServer = "127.0.0.1:3014" +var shtdnDialer = NetDialer{ + Address: shtdnServer, + User: dialer.User, + Password: dialer.Password, +} + var shtdnClntOpts = Opts{ - User: opts.User, - Pass: opts.Pass, - Timeout: 20 * time.Second, - Reconnect: 500 * time.Millisecond, - MaxReconnects: 10, - RequiredProtocolInfo: ProtocolInfo{Features: []iproto.Feature{iproto.IPROTO_FEATURE_WATCHERS}}, + Timeout: 20 * time.Second, + Reconnect: 500 * time.Millisecond, + MaxReconnects: 10, } var shtdnSrvOpts = test_helpers.StartOpts{ + Dialer: shtdnDialer, InitScript: "config.lua", Listen: shtdnServer, - User: shtdnClntOpts.User, - Pass: shtdnClntOpts.Pass, WaitStart: 100 * time.Millisecond, ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, @@ -139,7 +139,7 @@ func TestGracefulShutdown(t *testing.T) { require.Nil(t, err) defer test_helpers.StopTarantoolWithCleanup(inst) - conn = test_helpers.ConnectWithValidation(t, shtdnServer, shtdnClntOpts) + conn = test_helpers.ConnectWithValidation(t, shtdnDialer, shtdnClntOpts) defer conn.Close() testGracefulShutdown(t, conn, &inst) @@ -147,16 +147,18 @@ func TestGracefulShutdown(t *testing.T) { func TestCloseGraceful(t *testing.T) { opts := Opts{ - User: shtdnClntOpts.User, - Pass: shtdnClntOpts.Pass, Timeout: shtdnClntOpts.Timeout, } + testDialer := shtdnDialer + testDialer.RequiredProtocolInfo = ProtocolInfo{} + testSrvOpts := shtdnSrvOpts + testSrvOpts.Dialer = testDialer - inst, err := test_helpers.StartTarantool(shtdnSrvOpts) + inst, err := test_helpers.StartTarantool(testSrvOpts) require.Nil(t, err) defer test_helpers.StopTarantoolWithCleanup(inst) - conn := test_helpers.ConnectWithValidation(t, shtdnServer, opts) + conn := test_helpers.ConnectWithValidation(t, testDialer, opts) defer conn.Close() // Send request with sleep. @@ -197,7 +199,7 @@ func TestGracefulShutdownWithReconnect(t *testing.T) { require.Nil(t, err) defer test_helpers.StopTarantoolWithCleanup(inst) - conn := test_helpers.ConnectWithValidation(t, shtdnServer, shtdnClntOpts) + conn := test_helpers.ConnectWithValidation(t, shtdnDialer, shtdnClntOpts) defer conn.Close() testGracefulShutdown(t, conn, &inst) @@ -214,19 +216,23 @@ func TestGracefulShutdownWithReconnect(t *testing.T) { func TestNoGracefulShutdown(t *testing.T) { // No watchers = no graceful shutdown. - noShtdnClntOpts := shtdnClntOpts.Clone() - noShtdnClntOpts.RequiredProtocolInfo = ProtocolInfo{} + noSthdClntOpts := opts + noShtdDialer := shtdnDialer + noShtdDialer.RequiredProtocolInfo = ProtocolInfo{} test_helpers.SkipIfWatchersSupported(t) var inst test_helpers.TarantoolInstance var conn *Connection var err error - inst, err = test_helpers.StartTarantool(shtdnSrvOpts) + testSrvOpts := shtdnSrvOpts + testSrvOpts.Dialer = noShtdDialer + + inst, err = test_helpers.StartTarantool(testSrvOpts) require.Nil(t, err) defer test_helpers.StopTarantoolWithCleanup(inst) - conn = test_helpers.ConnectWithValidation(t, shtdnServer, noShtdnClntOpts) + conn = test_helpers.ConnectWithValidation(t, noShtdDialer, noSthdClntOpts) defer conn.Close() evalSleep := 10 // in seconds @@ -278,7 +284,7 @@ func TestGracefulShutdownRespectsClose(t *testing.T) { require.Nil(t, err) defer test_helpers.StopTarantoolWithCleanup(inst) - conn = test_helpers.ConnectWithValidation(t, shtdnServer, shtdnClntOpts) + conn = test_helpers.ConnectWithValidation(t, shtdnDialer, shtdnClntOpts) defer conn.Close() // Create a helper watcher to ensure that async @@ -358,7 +364,7 @@ func TestGracefulShutdownNotRacesWithRequestReconnect(t *testing.T) { require.Nil(t, err) defer test_helpers.StopTarantoolWithCleanup(inst) - conn = test_helpers.ConnectWithValidation(t, shtdnServer, shtdnClntOpts) + conn = test_helpers.ConnectWithValidation(t, shtdnDialer, shtdnClntOpts) defer conn.Close() // Create a helper watcher to ensure that async @@ -429,7 +435,7 @@ func TestGracefulShutdownCloseConcurrent(t *testing.T) { require.Nil(t, err) defer test_helpers.StopTarantoolWithCleanup(inst) - conn := test_helpers.ConnectWithValidation(t, shtdnServer, shtdnClntOpts) + conn := test_helpers.ConnectWithValidation(t, shtdnDialer, shtdnClntOpts) defer conn.Close() // Create a helper watcher to ensure that async @@ -466,7 +472,7 @@ func TestGracefulShutdownCloseConcurrent(t *testing.T) { // Do not wait till Tarantool register out watcher, // test everything is ok even on async. - conn, err := Connect(ctx, shtdnServer, shtdnClntOpts) + conn, err := Connect(ctx, shtdnDialer, shtdnClntOpts) if err != nil { t.Errorf("Failed to connect: %s", err) } else { @@ -519,7 +525,7 @@ func TestGracefulShutdownConcurrent(t *testing.T) { require.Nil(t, err) defer test_helpers.StopTarantoolWithCleanup(inst) - conn := test_helpers.ConnectWithValidation(t, shtdnServer, shtdnClntOpts) + conn := test_helpers.ConnectWithValidation(t, shtdnDialer, shtdnClntOpts) defer conn.Close() // Set a big timeout so it would be easy to differ @@ -542,7 +548,7 @@ func TestGracefulShutdownConcurrent(t *testing.T) { go func(i int) { defer caseWg.Done() - conn := test_helpers.ConnectWithValidation(t, shtdnServer, shtdnClntOpts) + conn := test_helpers.ConnectWithValidation(t, shtdnDialer, shtdnClntOpts) defer conn.Close() // Create a helper watcher to ensure that async diff --git a/ssl.go b/ssl.go index 8ca430559..70f324df6 100644 --- a/ssl.go +++ b/ssl.go @@ -15,8 +15,37 @@ import ( "github.com/tarantool/go-openssl" ) +type sslOpts struct { + // KeyFile is a path to a private SSL key file. + KeyFile string + // CertFile is a path to an SSL certificate file. + CertFile string + // CaFile is a path to a trusted certificate authorities (CA) file. + CaFile string + // Ciphers is a colon-separated (:) list of SSL cipher suites the connection + // can use. + // + // We don't provide a list of supported ciphers. This is what OpenSSL + // does. The only limitation is usage of TLSv1.2 (because other protocol + // versions don't seem to support the GOST cipher). To add additional + // ciphers (GOST cipher), you must configure OpenSSL. + // + // See also + // + // * https://www.openssl.org/docs/man1.1.1/man1/ciphers.html + Ciphers string + // Password is a password for decrypting the private SSL key file. + // The priority is as follows: try to decrypt with Password, then + // try PasswordFile. + Password string + // PasswordFile is a path to the list of passwords for decrypting + // the private SSL key file. The connection tries every line from the + // file as a password. + PasswordFile string +} + func sslDialContext(ctx context.Context, network, address string, - opts SslOpts) (connection net.Conn, err error) { + opts sslOpts) (connection net.Conn, err error) { var sslCtx interface{} if sslCtx, err = sslCreateContext(opts); err != nil { return @@ -27,7 +56,7 @@ func sslDialContext(ctx context.Context, network, address string, // interface{} is a hack. It helps to avoid dependency of go-openssl in build // of tests with the tag 'go_tarantool_ssl_disable'. -func sslCreateContext(opts SslOpts) (ctx interface{}, err error) { +func sslCreateContext(opts sslOpts) (ctx interface{}, err error) { var sslCtx *openssl.Ctx // Require TLSv1.2, because other protocol versions don't seem to diff --git a/ssl_test.go b/ssl_test.go index 65be85504..61f6e131d 100644 --- a/ssl_test.go +++ b/ssl_test.go @@ -4,98 +4,19 @@ package tarantool_test import ( - "context" - "errors" "fmt" - "io/ioutil" - "net" "os" - "strconv" "strings" "testing" "time" - "github.com/tarantool/go-openssl" . "github.com/tarantool/go-tarantool/v2" "github.com/tarantool/go-tarantool/v2/test_helpers" ) -const sslHost = "127.0.0.1" const tntHost = "127.0.0.1:3014" -func serverSsl(network, address string, opts SslOpts) (net.Listener, error) { - ctx, err := SslCreateContext(opts) - if err != nil { - return nil, errors.New("Unable to create SSL context: " + err.Error()) - } - - return openssl.Listen(network, address, ctx.(*openssl.Ctx)) -} - -func serverSslAccept(l net.Listener) (<-chan string, <-chan error) { - message := make(chan string, 1) - errors := make(chan error, 1) - - go func() { - conn, err := l.Accept() - if err != nil { - errors <- err - } else { - bytes, err := ioutil.ReadAll(conn) - if err != nil { - errors <- err - } else { - message <- string(bytes) - } - conn.Close() - } - - close(message) - close(errors) - }() - - return message, errors -} - -func serverSslRecv(msgs <-chan string, errs <-chan error) (string, error) { - return <-msgs, <-errs -} - -func clientSsl(ctx context.Context, network, address string, - opts SslOpts) (net.Conn, error) { - return SslDialContext(ctx, network, address, opts) -} - -func createClientServerSsl(ctx context.Context, t testing.TB, serverOpts, - clientOpts SslOpts) (net.Listener, net.Conn, <-chan string, <-chan error, error) { - t.Helper() - - l, err := serverSsl("tcp", sslHost+":0", serverOpts) - if err != nil { - t.Fatalf("Unable to create server, error %q", err.Error()) - } - - msgs, errs := serverSslAccept(l) - - port := l.Addr().(*net.TCPAddr).Port - c, err := clientSsl(ctx, "tcp", sslHost+":"+strconv.Itoa(port), clientOpts) - - return l, c, msgs, errs, err -} - -func createClientServerSslOk(ctx context.Context, t testing.TB, serverOpts, - clientOpts SslOpts) (net.Listener, net.Conn, <-chan string, <-chan error) { - t.Helper() - - l, c, msgs, errs, err := createClientServerSsl(ctx, t, serverOpts, clientOpts) - if err != nil { - t.Fatalf("Unable to create client, error %q", err.Error()) - } - - return l, c, msgs, errs -} - -func serverTnt(serverOpts SslOpts, auth Auth) (test_helpers.TarantoolInstance, error) { +func serverTnt(serverOpts SslTestOpts, auth Auth) (test_helpers.TarantoolInstance, error) { listen := tntHost + "?transport=ssl&" key := serverOpts.KeyFile @@ -130,37 +51,41 @@ func serverTnt(serverOpts SslOpts, auth Auth) (test_helpers.TarantoolInstance, e listen = listen[:len(listen)-1] - return test_helpers.StartTarantool(test_helpers.StartOpts{ - Auth: auth, - InitScript: "config.lua", - Listen: listen, - SslCertsDir: "testdata", - ClientServer: tntHost, - ClientTransport: "ssl", - ClientSsl: serverOpts, - User: "test", - Pass: "test", - WaitStart: 100 * time.Millisecond, - ConnectRetry: 10, - RetryTimeout: 500 * time.Millisecond, - }) + return test_helpers.StartTarantool( + test_helpers.StartOpts{ + Dialer: OpenSslDialer{ + Address: tntHost, + Auth: auth, + User: "test", + Password: "test", + SslKeyFile: serverOpts.KeyFile, + SslCertFile: serverOpts.CertFile, + SslCaFile: serverOpts.CaFile, + SslCiphers: serverOpts.Ciphers, + SslPassword: serverOpts.Password, + SslPasswordFile: serverOpts.PasswordFile, + }, + Auth: auth, + InitScript: "config.lua", + Listen: listen, + SslCertsDir: "testdata", + WaitStart: 100 * time.Millisecond, + ConnectRetry: 10, + RetryTimeout: 500 * time.Millisecond, + }, + ) } func serverTntStop(inst test_helpers.TarantoolInstance) { test_helpers.StopTarantoolWithCleanup(inst) } -func checkTntConn(clientOpts SslOpts) error { +func checkTntConn(dialer Dialer) error { ctx, cancel := test_helpers.GetConnectContext() defer cancel() - conn, err := Connect(ctx, tntHost, Opts{ - Auth: AutoAuth, + conn, err := Connect(ctx, dialer, Opts{ Timeout: 500 * time.Millisecond, - User: "test", - Pass: "test", SkipSchema: true, - Transport: "ssl", - Ssl: clientOpts, }) if err != nil { return err @@ -169,38 +94,7 @@ func checkTntConn(clientOpts SslOpts) error { return nil } -func assertConnectionSslFail(ctx context.Context, t testing.TB, serverOpts, - clientOpts SslOpts) { - t.Helper() - - l, c, _, _, err := createClientServerSsl(ctx, t, serverOpts, clientOpts) - l.Close() - if err == nil { - c.Close() - t.Errorf("An unexpected connection to the server.") - } -} - -func assertConnectionSslOk(ctx context.Context, t testing.TB, serverOpts, - clientOpts SslOpts) { - t.Helper() - - l, c, msgs, errs := createClientServerSslOk(ctx, t, serverOpts, clientOpts) - const message = "any test string" - c.Write([]byte(message)) - c.Close() - - recv, err := serverSslRecv(msgs, errs) - l.Close() - - if err != nil { - t.Errorf("An unexpected server error: %q", err.Error()) - } else if recv != message { - t.Errorf("An unexpected server message: %q, expected %q", recv, message) - } -} - -func assertConnectionTntFail(t testing.TB, serverOpts, clientOpts SslOpts) { +func assertConnectionTntFail(t testing.TB, serverOpts SslTestOpts, dialer OpenSslDialer) { t.Helper() inst, err := serverTnt(serverOpts, AutoAuth) @@ -209,13 +103,13 @@ func assertConnectionTntFail(t testing.TB, serverOpts, clientOpts SslOpts) { t.Fatalf("An unexpected server error %q", err.Error()) } - err = checkTntConn(clientOpts) + err = checkTntConn(dialer) if err == nil { t.Errorf("An unexpected connection to the server") } } -func assertConnectionTntOk(t testing.TB, serverOpts, clientOpts SslOpts) { +func assertConnectionTntOk(t testing.TB, serverOpts SslTestOpts, dialer OpenSslDialer) { t.Helper() inst, err := serverTnt(serverOpts, AutoAuth) @@ -224,17 +118,17 @@ func assertConnectionTntOk(t testing.TB, serverOpts, clientOpts SslOpts) { t.Fatalf("An unexpected server error %q", err.Error()) } - err = checkTntConn(clientOpts) + err = checkTntConn(dialer) if err != nil { t.Errorf("An unexpected connection error %q", err.Error()) } } -type test struct { +type sslTest struct { name string ok bool - serverOpts SslOpts - clientOpts SslOpts + serverOpts SslTestOpts + clientOpts SslTestOpts } /* @@ -253,24 +147,24 @@ CertFile - optional, mandatory if server.CaFile set CaFile - optional, Ciphers - optional */ -var tests = []test{ +var sslTests = []sslTest{ { "key_crt_server", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", }, - SslOpts{}, + SslTestOpts{}, }, { "key_crt_server_and_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", }, @@ -278,22 +172,22 @@ var tests = []test{ { "key_crt_ca_server", false, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{}, + SslTestOpts{}, }, { "key_crt_ca_server_key_crt_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", }, @@ -301,12 +195,12 @@ var tests = []test{ { "key_crt_ca_server_and_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", @@ -315,12 +209,12 @@ var tests = []test{ { "key_crt_ca_server_and_client_invalid_path_key", false, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "any_invalid_path", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", @@ -329,12 +223,12 @@ var tests = []test{ { "key_crt_ca_server_and_client_invalid_path_crt", false, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "any_invalid_path", CaFile: "testdata/ca.crt", @@ -343,12 +237,12 @@ var tests = []test{ { "key_crt_ca_server_and_client_invalid_path_ca", false, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "any_invalid_path", @@ -357,12 +251,12 @@ var tests = []test{ { "key_crt_ca_server_and_client_empty_key", false, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/empty", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", @@ -371,12 +265,12 @@ var tests = []test{ { "key_crt_ca_server_and_client_empty_crt", false, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/empty", CaFile: "testdata/ca.crt", @@ -385,12 +279,12 @@ var tests = []test{ { "key_crt_ca_server_and_client_empty_ca", false, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/empty", @@ -399,11 +293,11 @@ var tests = []test{ { "key_crt_server_and_key_crt_ca_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", @@ -412,13 +306,13 @@ var tests = []test{ { "key_crt_ca_ciphers_server_key_crt_ca_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", @@ -427,13 +321,13 @@ var tests = []test{ { "key_crt_ca_ciphers_server_and_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", @@ -443,13 +337,13 @@ var tests = []test{ { "non_equal_ciphers_client", false, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", @@ -459,12 +353,12 @@ var tests = []test{ { "pass_key_encrypt_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.enc.key", CertFile: "testdata/localhost.crt", Password: "mysslpassword", @@ -473,12 +367,12 @@ var tests = []test{ { "passfile_key_encrypt_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.enc.key", CertFile: "testdata/localhost.crt", PasswordFile: "testdata/passwords", @@ -487,12 +381,12 @@ var tests = []test{ { "pass_and_passfile_key_encrypt_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.enc.key", CertFile: "testdata/localhost.crt", Password: "mysslpassword", @@ -502,12 +396,12 @@ var tests = []test{ { "inv_pass_and_passfile_key_encrypt_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.enc.key", CertFile: "testdata/localhost.crt", Password: "invalidpassword", @@ -517,12 +411,12 @@ var tests = []test{ { "pass_and_inv_passfile_key_encrypt_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.enc.key", CertFile: "testdata/localhost.crt", Password: "mysslpassword", @@ -532,12 +426,12 @@ var tests = []test{ { "pass_and_not_existing_passfile_key_encrypt_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.enc.key", CertFile: "testdata/localhost.crt", Password: "mysslpassword", @@ -547,12 +441,12 @@ var tests = []test{ { "inv_pass_and_inv_passfile_key_encrypt_client", false, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.enc.key", CertFile: "testdata/localhost.crt", Password: "invalidpassword", @@ -562,12 +456,12 @@ var tests = []test{ { "not_existing_passfile_key_encrypt_client", false, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.enc.key", CertFile: "testdata/localhost.crt", PasswordFile: "testdata/notafile", @@ -576,12 +470,12 @@ var tests = []test{ { "no_pass_key_encrypt_client", false, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.enc.key", CertFile: "testdata/localhost.crt", }, @@ -589,12 +483,12 @@ var tests = []test{ { "pass_key_non_encrypt_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", Password: "invalidpassword", @@ -603,12 +497,12 @@ var tests = []test{ { "passfile_key_non_encrypt_client", true, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", CaFile: "testdata/ca.crt", }, - SslOpts{ + SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", PasswordFile: "testdata/invalidpasswords", @@ -622,67 +516,40 @@ func isTestTntSsl() bool { (testTntSsl == "1" || strings.ToUpper(testTntSsl) == "TRUE") } +func makeOpenSslDialer(opts SslTestOpts) OpenSslDialer { + return OpenSslDialer{ + Address: tntHost, + User: "test", + Password: "test", + SslKeyFile: opts.KeyFile, + SslCertFile: opts.CertFile, + SslCaFile: opts.CaFile, + SslCiphers: opts.Ciphers, + SslPassword: opts.Password, + SslPasswordFile: opts.PasswordFile, + } +} + func TestSslOpts(t *testing.T) { isTntSsl := isTestTntSsl() - for _, test := range tests { - var ctx context.Context - var cancel context.CancelFunc - ctx, cancel = test_helpers.GetConnectContext() - if test.ok { - t.Run("ok_ssl_"+test.name, func(t *testing.T) { - assertConnectionSslOk(ctx, t, test.serverOpts, test.clientOpts) - }) - } else { - t.Run("fail_ssl_"+test.name, func(t *testing.T) { - assertConnectionSslFail(ctx, t, test.serverOpts, test.clientOpts) - }) - } - cancel() + for _, test := range sslTests { if !isTntSsl { continue } + dialer := makeOpenSslDialer(test.clientOpts) if test.ok { t.Run("ok_tnt_"+test.name, func(t *testing.T) { - assertConnectionTntOk(t, test.serverOpts, test.clientOpts) + assertConnectionTntOk(t, test.serverOpts, dialer) }) } else { t.Run("fail_tnt_"+test.name, func(t *testing.T) { - assertConnectionTntFail(t, test.serverOpts, test.clientOpts) + assertConnectionTntFail(t, test.serverOpts, dialer) }) } } } -func TestSslDialContextCancel(t *testing.T) { - serverOpts := SslOpts{ - KeyFile: "testdata/localhost.key", - CertFile: "testdata/localhost.crt", - CaFile: "testdata/ca.crt", - Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", - } - clientOpts := SslOpts{ - KeyFile: "testdata/localhost.key", - CertFile: "testdata/localhost.crt", - CaFile: "testdata/ca.crt", - Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", - } - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - l, c, _, _, err := createClientServerSsl(ctx, t, serverOpts, clientOpts) - l.Close() - - if err == nil { - c.Close() - t.Fatalf("Expected error, dial was not canceled") - } - if !strings.Contains(err.Error(), "operation was canceled") { - t.Fatalf("Unexpected error, expected to contain %s, got %v", - "operation was canceled", err) - } -} - func TestOpts_PapSha256Auth(t *testing.T) { isTntSsl := isTestTntSsl() if !isTntSsl { @@ -691,13 +558,13 @@ func TestOpts_PapSha256Auth(t *testing.T) { isLess, err := test_helpers.IsTarantoolVersionLess(2, 11, 0) if err != nil { - t.Fatalf("Could not check Tarantool version.") + t.Fatalf("Could not check Tarantool version: %s", err) } if isLess { - t.Skip("Skipping test for Tarantoo without pap-sha256 support") + t.Skip("Skipping test for Tarantool without pap-sha256 support") } - sslOpts := SslOpts{ + sslOpts := SslTestOpts{ KeyFile: "testdata/localhost.key", CertFile: "testdata/localhost.crt", } @@ -708,14 +575,20 @@ func TestOpts_PapSha256Auth(t *testing.T) { t.Fatalf("An unexpected server error %q", err.Error()) } - clientOpts := opts - clientOpts.Transport = "ssl" - clientOpts.Ssl = sslOpts - clientOpts.Auth = PapSha256Auth - conn := test_helpers.ConnectWithValidation(t, tntHost, clientOpts) + client := OpenSslDialer{ + Address: tntHost, + Auth: PapSha256Auth, + User: "test", + Password: "test", + RequiredProtocolInfo: ProtocolInfo{}, + SslKeyFile: sslOpts.KeyFile, + SslCertFile: sslOpts.CertFile, + } + + conn := test_helpers.ConnectWithValidation(t, client, opts) conn.Close() - clientOpts.Auth = AutoAuth - conn = test_helpers.ConnectWithValidation(t, tntHost, clientOpts) + client.Auth = AutoAuth + conn = test_helpers.ConnectWithValidation(t, client, opts) conn.Close() } diff --git a/tarantool_test.go b/tarantool_test.go index f511376c2..e54c6a46d 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -25,15 +25,20 @@ import ( ) var startOpts test_helpers.StartOpts = test_helpers.StartOpts{ + Dialer: dialer, InitScript: "config.lua", Listen: server, - User: opts.User, - Pass: opts.Pass, WaitStart: 100 * time.Millisecond, ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, } +var dialer = NetDialer{ + Address: server, + User: "test", + Password: "test", +} + type Member struct { Name string Nonce string @@ -78,8 +83,6 @@ var indexNo = uint32(0) var indexName = "primary" var opts = Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", //Concurrency: 32, //RateLimit: 4*1024, } @@ -89,7 +92,7 @@ const N = 500 func BenchmarkClientSerial(b *testing.B) { var err error - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err = conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -109,7 +112,7 @@ func BenchmarkClientSerial(b *testing.B) { func BenchmarkClientSerialRequestObject(b *testing.B) { var err error - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err = conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -136,7 +139,7 @@ func BenchmarkClientSerialRequestObject(b *testing.B) { func BenchmarkClientSerialRequestObjectWithContext(b *testing.B) { var err error - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err = conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -165,7 +168,7 @@ func BenchmarkClientSerialRequestObjectWithContext(b *testing.B) { func BenchmarkClientSerialTyped(b *testing.B) { var err error - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err = conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -186,7 +189,7 @@ func BenchmarkClientSerialTyped(b *testing.B) { func BenchmarkClientSerialSQL(b *testing.B) { test_helpers.SkipIfSQLUnsupported(b) - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err := conn.Replace("SQL_TEST", []interface{}{uint(1111), "hello", "world"}) @@ -208,7 +211,7 @@ func BenchmarkClientSerialSQL(b *testing.B) { func BenchmarkClientSerialSQLPrepared(b *testing.B) { test_helpers.SkipIfSQLUnsupported(b) - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err := conn.Replace("SQL_TEST", []interface{}{uint(1111), "hello", "world"}) @@ -240,7 +243,7 @@ func BenchmarkClientSerialSQLPrepared(b *testing.B) { func BenchmarkClientFuture(b *testing.B) { var err error - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err = conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -267,7 +270,7 @@ func BenchmarkClientFuture(b *testing.B) { func BenchmarkClientFutureTyped(b *testing.B) { var err error - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err = conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -297,7 +300,7 @@ func BenchmarkClientFutureTyped(b *testing.B) { func BenchmarkClientFutureParallel(b *testing.B) { var err error - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err = conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -330,7 +333,7 @@ func BenchmarkClientFutureParallel(b *testing.B) { func BenchmarkClientFutureParallelTyped(b *testing.B) { var err error - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err = conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -366,7 +369,7 @@ func BenchmarkClientFutureParallelTyped(b *testing.B) { } func BenchmarkClientParallel(b *testing.B) { - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err := conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -387,7 +390,7 @@ func BenchmarkClientParallel(b *testing.B) { } func benchmarkClientParallelRequestObject(multiplier int, b *testing.B) { - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err := conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -415,7 +418,7 @@ func benchmarkClientParallelRequestObject(multiplier int, b *testing.B) { } func benchmarkClientParallelRequestObjectWithContext(multiplier int, b *testing.B) { - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err := conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -447,7 +450,7 @@ func benchmarkClientParallelRequestObjectWithContext(multiplier int, b *testing. } func benchmarkClientParallelRequestObjectMixed(multiplier int, b *testing.B) { - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err := conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -486,7 +489,7 @@ func benchmarkClientParallelRequestObjectMixed(multiplier int, b *testing.B) { func BenchmarkClientParallelRequestObject(b *testing.B) { multipliers := []int{10, 50, 500, 1000} - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err := conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -512,7 +515,7 @@ func BenchmarkClientParallelRequestObject(b *testing.B) { } func BenchmarkClientParallelMassive(b *testing.B) { - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err := conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -547,7 +550,7 @@ func BenchmarkClientParallelMassive(b *testing.B) { } func BenchmarkClientParallelMassiveUntyped(b *testing.B) { - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err := conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) @@ -582,7 +585,7 @@ func BenchmarkClientParallelMassiveUntyped(b *testing.B) { } func BenchmarkClientReplaceParallel(b *testing.B) { - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() b.ResetTimer() @@ -597,7 +600,7 @@ func BenchmarkClientReplaceParallel(b *testing.B) { } func BenchmarkClientLargeSelectParallel(b *testing.B) { - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() offset, limit := uint32(0), uint32(1000) @@ -616,7 +619,7 @@ func BenchmarkClientLargeSelectParallel(b *testing.B) { func BenchmarkClientParallelSQL(b *testing.B) { test_helpers.SkipIfSQLUnsupported(b) - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err := conn.Replace("SQL_TEST", []interface{}{uint(1111), "hello", "world"}) @@ -640,7 +643,7 @@ func BenchmarkClientParallelSQL(b *testing.B) { func BenchmarkClientParallelSQLPrepared(b *testing.B) { test_helpers.SkipIfSQLUnsupported(b) - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err := conn.Replace("SQL_TEST", []interface{}{uint(1111), "hello", "world"}) @@ -674,7 +677,7 @@ func BenchmarkClientParallelSQLPrepared(b *testing.B) { func BenchmarkSQLSerial(b *testing.B) { test_helpers.SkipIfSQLUnsupported(b) - conn := test_helpers.ConnectWithValidation(b, server, opts) + conn := test_helpers.ConnectWithValidation(b, dialer, opts) defer conn.Close() _, err := conn.Replace("SQL_TEST", []interface{}{uint(1111), "hello", "world"}) @@ -693,19 +696,18 @@ func BenchmarkSQLSerial(b *testing.B) { } } -func TestTtDialer(t *testing.T) { +func TestNetDialer(t *testing.T) { assert := assert.New(t) require := require.New(t) ctx, cancel := test_helpers.GetConnectContext() defer cancel() - conn, err := TtDialer{}.Dial(ctx, server, DialOpts{}) + conn, err := dialer.Dial(ctx, DialOpts{}) require.Nil(err) require.NotNil(conn) defer conn.Close() - assert.Contains(conn.LocalAddr().String(), "127.0.0.1") - assert.Equal(server, conn.RemoteAddr().String()) + assert.Equal(server, conn.Addr().String()) assert.NotEqual("", conn.Greeting().Version) // Write IPROTO_PING. @@ -738,53 +740,8 @@ func TestTtDialer(t *testing.T) { assert.Equal([]byte{0x83, 0x00, 0xce, 0x00, 0x00, 0x00, 0x00}, buf[:7]) } -func TestTtDialer_worksWithConnection(t *testing.T) { - defaultOpts := opts - defaultOpts.Dialer = TtDialer{} - - conn := test_helpers.ConnectWithValidation(t, server, defaultOpts) - defer conn.Close() - - _, err := conn.Do(NewPingRequest()).Get() - assert.Nil(t, err) -} - -func TestOptsAuth_Default(t *testing.T) { - defaultOpts := opts - defaultOpts.Auth = AutoAuth - - conn := test_helpers.ConnectWithValidation(t, server, defaultOpts) - defer conn.Close() -} - -func TestOptsAuth_ChapSha1Auth(t *testing.T) { - chapSha1Opts := opts - chapSha1Opts.Auth = ChapSha1Auth - - conn := test_helpers.ConnectWithValidation(t, server, chapSha1Opts) - defer conn.Close() -} - -func TestOptsAuth_PapSha256AuthForbit(t *testing.T) { - papSha256Opts := opts - papSha256Opts.Auth = PapSha256Auth - - ctx, cancel := test_helpers.GetConnectContext() - defer cancel() - conn, err := Connect(ctx, server, papSha256Opts) - if err == nil { - t.Error("An error expected.") - conn.Close() - } - - if err.Error() != "failed to authenticate: forbidden to use pap-sha256"+ - " unless SSL is enabled for the connection" { - t.Errorf("An unexpected error: %s", err) - } -} - func TestFutureMultipleGetGetTyped(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() fut := conn.Call17Async("simple_concat", []interface{}{"1"}) @@ -822,7 +779,7 @@ func TestFutureMultipleGetGetTyped(t *testing.T) { } func TestFutureMultipleGetWithError(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() fut := conn.Call17Async("non_exist", []interface{}{"1"}) @@ -835,7 +792,7 @@ func TestFutureMultipleGetWithError(t *testing.T) { } func TestFutureMultipleGetTypedWithError(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() fut := conn.Call17Async("simple_concat", []interface{}{"1"}) @@ -864,7 +821,7 @@ func TestClient(t *testing.T) { var resp *Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Ping @@ -1204,7 +1161,7 @@ func TestClient(t *testing.T) { } func TestClientSessionPush(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() var it ResponseIterator @@ -1358,7 +1315,7 @@ func TestSQL(t *testing.T) { selectSpanDifQuery := selectSpanDifQueryNew if isSeqScanOld, err := test_helpers.IsTarantoolVersionLess(3, 0, 0); err != nil { - t.Fatal("Could not check the Tarantool version") + t.Fatalf("Could not check the Tarantool version: %s", err) } else if isSeqScanOld { selectSpanDifQuery = selectSpanDifQueryOld } @@ -1504,7 +1461,7 @@ func TestSQL(t *testing.T) { }, } - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() for i, test := range testCases { @@ -1535,7 +1492,7 @@ func TestSQL(t *testing.T) { func TestSQLTyped(t *testing.T) { test_helpers.SkipIfSQLUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() mem := []Member{} @@ -1564,7 +1521,7 @@ func TestSQLBindings(t *testing.T) { var resp *Response - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // test all types of supported bindings @@ -1666,7 +1623,7 @@ func TestStressSQL(t *testing.T) { var resp *Response - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() resp, err := conn.Execute(createTableQuery, []interface{}{}) @@ -1763,7 +1720,7 @@ func TestStressSQL(t *testing.T) { func TestNewPrepared(t *testing.T) { test_helpers.SkipIfSQLUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() stmt, err := conn.NewPrepared(selectNamedQuery2) @@ -1852,7 +1809,7 @@ func TestConnection_DoWithStrangerConn(t *testing.T) { } func TestGetSchema(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() s, err := GetSchema(conn) @@ -1865,7 +1822,7 @@ func TestGetSchema(t *testing.T) { } func TestConnection_SetSchema_Changes(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() req := NewInsertRequest(spaceName) @@ -1929,7 +1886,7 @@ func TestNewPreparedFromResponse(t *testing.T) { } func TestSchema(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Schema @@ -2079,7 +2036,7 @@ func TestSchema(t *testing.T) { } func TestSchema_IsNullable(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() schema, err := GetSchema(conn) @@ -2118,7 +2075,7 @@ func TestClientNamed(t *testing.T) { var resp *Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Insert @@ -2215,7 +2172,7 @@ func TestClientRequestObjects(t *testing.T) { err error ) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Ping @@ -2506,7 +2463,7 @@ func TestClientRequestObjects(t *testing.T) { // Tarantool supports SQL since version 2.0.0 isLess, err := test_helpers.IsTarantoolVersionLess(2, 0, 0) if err != nil { - t.Fatalf("Could not check the Tarantool version") + t.Fatalf("Could not check the Tarantool version: %s", err) } if isLess { return @@ -2609,7 +2566,7 @@ func testConnectionDoSelectRequestCheck(t *testing.T, } func TestConnectionDoSelectRequest(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() testConnectionDoSelectRequestPrepare(t, conn) @@ -2627,7 +2584,7 @@ func TestConnectionDoSelectRequest(t *testing.T) { func TestConnectionDoWatchOnceRequest(t *testing.T) { test_helpers.SkipIfWatchOnceUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() _, err := conn.Do(NewBroadcastRequest("hello").Value("world")).Get() @@ -2650,13 +2607,13 @@ func TestConnectionDoWatchOnceRequest(t *testing.T) { func TestConnectionDoWatchOnceOnEmptyKey(t *testing.T) { watchOnceNotSupported, err := test_helpers.IsTarantoolVersionLess(3, 0, 0) if err != nil { - log.Fatalf("Could not check the Tarantool version") + log.Fatalf("Could not check the Tarantool version: %s", err) } if watchOnceNotSupported { return } - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() resp, err := conn.Do(NewWatchOnceRequest("notexists!")).Get() @@ -2674,7 +2631,7 @@ func TestConnectionDoWatchOnceOnEmptyKey(t *testing.T) { func TestConnectionDoSelectRequest_fetch_pos(t *testing.T) { test_helpers.SkipIfPaginationUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() testConnectionDoSelectRequestPrepare(t, conn) @@ -2693,7 +2650,7 @@ func TestConnectionDoSelectRequest_fetch_pos(t *testing.T) { func TestConnectDoSelectRequest_after_tuple(t *testing.T) { test_helpers.SkipIfPaginationUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() testConnectionDoSelectRequestPrepare(t, conn) @@ -2713,7 +2670,7 @@ func TestConnectDoSelectRequest_after_tuple(t *testing.T) { func TestConnectionDoSelectRequest_pagination_pos(t *testing.T) { test_helpers.SkipIfPaginationUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() testConnectionDoSelectRequestPrepare(t, conn) @@ -2737,7 +2694,7 @@ func TestConnection_Call(t *testing.T) { var resp *Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() resp, err = conn.Call("simple_concat", []interface{}{"1"}) @@ -2753,7 +2710,7 @@ func TestCallRequest(t *testing.T) { var resp *Response var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() req := NewCallRequest("simple_concat").Args([]interface{}{"1"}) @@ -2767,7 +2724,7 @@ func TestCallRequest(t *testing.T) { } func TestClientRequestObjectsWithNilContext(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() req := NewPingRequest().Context(nil) //nolint resp, err := conn.Do(req).Get() @@ -2783,7 +2740,7 @@ func TestClientRequestObjectsWithNilContext(t *testing.T) { } func TestClientRequestObjectsWithPassedCanceledContext(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() ctx, cancel := context.WithCancel(context.Background()) @@ -2825,7 +2782,7 @@ func (req *waitCtxRequest) Async() bool { func TestClientRequestObjectsWithContext(t *testing.T) { var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() ctx, cancel := context.WithCancel(context.Background()) @@ -2864,7 +2821,7 @@ func TestClientRequestObjectsWithContext(t *testing.T) { func TestComplexStructs(t *testing.T) { var err error - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() tuple := Tuple2{Cid: 777, Orig: "orig", Members: []Member{{"lol", "", 1}, {"wut", "", 3}}} @@ -2895,7 +2852,7 @@ func TestComplexStructs(t *testing.T) { func TestStream_IdValues(t *testing.T) { test_helpers.SkipIfStreamsUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() cases := []uint64{ @@ -2932,7 +2889,7 @@ func TestStream_Commit(t *testing.T) { test_helpers.SkipIfStreamsUnsupported(t) - conn = test_helpers.ConnectWithValidation(t, server, opts) + conn = test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() stream, _ := conn.NewStream() @@ -3047,7 +3004,7 @@ func TestStream_Rollback(t *testing.T) { test_helpers.SkipIfStreamsUnsupported(t) - conn = test_helpers.ConnectWithValidation(t, server, opts) + conn = test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() stream, _ := conn.NewStream() @@ -3168,7 +3125,7 @@ func TestStream_TxnIsolationLevel(t *testing.T) { test_helpers.SkipIfStreamsUnsupported(t) - conn = test_helpers.ConnectWithValidation(t, server, opts) + conn = test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() stream, _ := conn.NewStream() @@ -3269,7 +3226,7 @@ func TestStream_DoWithClosedConn(t *testing.T) { test_helpers.SkipIfStreamsUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) stream, _ := conn.NewStream() conn.Close() @@ -3288,7 +3245,7 @@ func TestStream_DoWithClosedConn(t *testing.T) { func TestConnectionProtocolInfoSupported(t *testing.T) { test_helpers.SkipIfIdUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // First Tarantool protocol version (1, IPROTO_FEATURE_STREAMS and @@ -3309,23 +3266,7 @@ func TestConnectionProtocolInfoSupported(t *testing.T) { }, } - clientProtocolInfo := conn.ClientProtocolInfo() - require.Equal(t, - clientProtocolInfo, - ProtocolInfo{ - Version: ProtocolVersion(6), - Features: []iproto.Feature{ - iproto.IPROTO_FEATURE_STREAMS, - iproto.IPROTO_FEATURE_TRANSACTIONS, - iproto.IPROTO_FEATURE_ERROR_EXTENSION, - iproto.IPROTO_FEATURE_WATCHERS, - iproto.IPROTO_FEATURE_PAGINATION, - iproto.IPROTO_FEATURE_SPACE_AND_INDEX_NAMES, - iproto.IPROTO_FEATURE_WATCH_ONCE, - }, - }) - - serverProtocolInfo := conn.ServerProtocolInfo() + serverProtocolInfo := conn.ProtocolInfo() require.GreaterOrEqual(t, serverProtocolInfo.Version, tarantool210ProtocolInfo.Version) @@ -3337,7 +3278,7 @@ func TestConnectionProtocolInfoSupported(t *testing.T) { func TestClientIdRequestObject(t *testing.T) { test_helpers.SkipIfIdUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() tarantool210ProtocolInfo := ProtocolInfo{ @@ -3373,7 +3314,7 @@ func TestClientIdRequestObject(t *testing.T) { func TestClientIdRequestObjectWithNilContext(t *testing.T) { test_helpers.SkipIfIdUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() tarantool210ProtocolInfo := ProtocolInfo{ @@ -3407,7 +3348,7 @@ func TestClientIdRequestObjectWithNilContext(t *testing.T) { } func TestClientIdRequestObjectWithPassedCanceledContext(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() ctx, cancel := context.WithCancel(context.Background()) @@ -3425,66 +3366,41 @@ func TestClientIdRequestObjectWithPassedCanceledContext(t *testing.T) { func TestConnectionProtocolInfoUnsupported(t *testing.T) { test_helpers.SkipIfIdSupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() - clientProtocolInfo := conn.ClientProtocolInfo() - require.Equal(t, - clientProtocolInfo, - ProtocolInfo{ - Version: ProtocolVersion(6), - Features: []iproto.Feature{ - iproto.IPROTO_FEATURE_STREAMS, - iproto.IPROTO_FEATURE_TRANSACTIONS, - iproto.IPROTO_FEATURE_ERROR_EXTENSION, - iproto.IPROTO_FEATURE_WATCHERS, - iproto.IPROTO_FEATURE_PAGINATION, - iproto.IPROTO_FEATURE_SPACE_AND_INDEX_NAMES, - iproto.IPROTO_FEATURE_WATCH_ONCE, - }, - }) - - serverProtocolInfo := conn.ServerProtocolInfo() - require.Equal(t, serverProtocolInfo, ProtocolInfo{}) -} - -func TestConnectionClientFeaturesUmmutable(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) - defer conn.Close() - - info := conn.ClientProtocolInfo() - infoOrig := info.Clone() - info.Features[0] = iproto.Feature(15532) - - require.Equal(t, conn.ClientProtocolInfo(), infoOrig) - require.NotEqual(t, conn.ClientProtocolInfo(), info) + serverProtocolInfo := conn.ProtocolInfo() + expected := ProtocolInfo{ + Auth: ChapSha1Auth, + } + require.Equal(t, expected, serverProtocolInfo) } -func TestConnectionServerFeaturesUmmutable(t *testing.T) { +func TestConnectionServerFeaturesImmutable(t *testing.T) { test_helpers.SkipIfIdUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() - info := conn.ServerProtocolInfo() + info := conn.ProtocolInfo() infoOrig := info.Clone() info.Features[0] = iproto.Feature(15532) - require.Equal(t, conn.ServerProtocolInfo(), infoOrig) - require.NotEqual(t, conn.ServerProtocolInfo(), info) + require.Equal(t, conn.ProtocolInfo(), infoOrig) + require.NotEqual(t, conn.ProtocolInfo(), info) } func TestConnectionProtocolVersionRequirementSuccess(t *testing.T) { test_helpers.SkipIfIdUnsupported(t) - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo = ProtocolInfo{ + testDialer := dialer + testDialer.RequiredProtocolInfo = ProtocolInfo{ Version: ProtocolVersion(3), } ctx, cancel := test_helpers.GetConnectContext() defer cancel() - conn, err := Connect(ctx, server, connOpts) + conn, err := Connect(ctx, testDialer, opts) require.Nilf(t, err, "No errors on connect") require.NotNilf(t, conn, "Connect success") @@ -3495,14 +3411,14 @@ func TestConnectionProtocolVersionRequirementSuccess(t *testing.T) { func TestConnectionProtocolVersionRequirementFail(t *testing.T) { test_helpers.SkipIfIdSupported(t) - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo = ProtocolInfo{ + testDialer := dialer + testDialer.RequiredProtocolInfo = ProtocolInfo{ Version: ProtocolVersion(3), } ctx, cancel := test_helpers.GetConnectContext() defer cancel() - conn, err := Connect(ctx, server, connOpts) + conn, err := Connect(ctx, testDialer, opts) require.Nilf(t, conn, "Connect fail") require.NotNilf(t, err, "Got error on connect") @@ -3512,14 +3428,14 @@ func TestConnectionProtocolVersionRequirementFail(t *testing.T) { func TestConnectionProtocolFeatureRequirementSuccess(t *testing.T) { test_helpers.SkipIfIdUnsupported(t) - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo = ProtocolInfo{ + testDialer := dialer + testDialer.RequiredProtocolInfo = ProtocolInfo{ Features: []iproto.Feature{iproto.IPROTO_FEATURE_TRANSACTIONS}, } ctx, cancel := test_helpers.GetConnectContext() defer cancel() - conn, err := Connect(ctx, server, connOpts) + conn, err := Connect(ctx, testDialer, opts) require.NotNilf(t, conn, "Connect success") require.Nilf(t, err, "No errors on connect") @@ -3530,14 +3446,14 @@ func TestConnectionProtocolFeatureRequirementSuccess(t *testing.T) { func TestConnectionProtocolFeatureRequirementFail(t *testing.T) { test_helpers.SkipIfIdSupported(t) - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo = ProtocolInfo{ + testDialer := dialer + testDialer.RequiredProtocolInfo = ProtocolInfo{ Features: []iproto.Feature{iproto.IPROTO_FEATURE_TRANSACTIONS}, } ctx, cancel := test_helpers.GetConnectContext() defer cancel() - conn, err := Connect(ctx, server, connOpts) + conn, err := Connect(ctx, testDialer, opts) require.Nilf(t, conn, "Connect fail") require.NotNilf(t, err, "Got error on connect") @@ -3549,15 +3465,15 @@ func TestConnectionProtocolFeatureRequirementFail(t *testing.T) { func TestConnectionProtocolFeatureRequirementManyFail(t *testing.T) { test_helpers.SkipIfIdSupported(t) - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo = ProtocolInfo{ + testDialer := dialer + testDialer.RequiredProtocolInfo = ProtocolInfo{ Features: []iproto.Feature{iproto.IPROTO_FEATURE_TRANSACTIONS, iproto.Feature(15532)}, } ctx, cancel := test_helpers.GetConnectContext() defer cancel() - conn, err := Connect(ctx, server, connOpts) + conn, err := Connect(ctx, testDialer, opts) require.Nilf(t, conn, "Connect fail") require.NotNilf(t, err, "Got error on connect") @@ -3567,49 +3483,10 @@ func TestConnectionProtocolFeatureRequirementManyFail(t *testing.T) { "Feature(15532) are not supported") } -func TestConnectionFeatureOptsImmutable(t *testing.T) { - test_helpers.SkipIfIdUnsupported(t) - - restartOpts := startOpts - restartOpts.Listen = "127.0.0.1:3014" - inst, err := test_helpers.StartTarantool(restartOpts) - defer test_helpers.StopTarantoolWithCleanup(inst) - - if err != nil { - log.Printf("Failed to prepare test tarantool: %s", err) - return - } - - retries := uint(10) - timeout := 100 * time.Millisecond - - connOpts := opts.Clone() - connOpts.Reconnect = timeout - connOpts.MaxReconnects = retries - connOpts.RequiredProtocolInfo = ProtocolInfo{ - Features: []iproto.Feature{iproto.IPROTO_FEATURE_TRANSACTIONS}, - } - - // Connect with valid opts - conn := test_helpers.ConnectWithValidation(t, server, connOpts) - defer conn.Close() - - // Change opts outside - connOpts.RequiredProtocolInfo.Features[0] = iproto.Feature(15532) - - // Trigger reconnect with opts re-check - test_helpers.StopTarantool(inst) - err = test_helpers.RestartTarantool(&inst) - require.Nilf(t, err, "Failed to restart tarantool") - - connected := test_helpers.WaitUntilReconnected(conn, retries, timeout) - require.True(t, connected, "Reconnect success") -} - func TestErrorExtendedInfoBasic(t *testing.T) { test_helpers.SkipIfErrorExtendedInfoUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() _, err := conn.Eval("not a Lua code", []interface{}{}) @@ -3637,7 +3514,7 @@ func TestErrorExtendedInfoBasic(t *testing.T) { func TestErrorExtendedInfoStack(t *testing.T) { test_helpers.SkipIfErrorExtendedInfoUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() _, err := conn.Eval("error(chained_error)", []interface{}{}) @@ -3673,7 +3550,7 @@ func TestErrorExtendedInfoStack(t *testing.T) { func TestErrorExtendedInfoFields(t *testing.T) { test_helpers.SkipIfErrorExtendedInfoUnsupported(t) - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() _, err := conn.Eval("error(access_denied_error)", []interface{}{}) @@ -3707,11 +3584,7 @@ func TestConnection_NewWatcher(t *testing.T) { test_helpers.SkipIfWatchersUnsupported(t) const key = "TestConnection_NewWatcher" - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - conn := test_helpers.ConnectWithValidation(t, server, connOpts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() events := make(chan WatchEvent) @@ -3742,17 +3615,19 @@ func TestConnection_NewWatcher(t *testing.T) { } func TestConnection_NewWatcher_noWatchersFeature(t *testing.T) { + test_helpers.SkipIfWatchersSupported(t) + const key = "TestConnection_NewWatcher_noWatchersFeature" - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []iproto.Feature{} - conn := test_helpers.ConnectWithValidation(t, server, connOpts) + testDialer := dialer + testDialer.RequiredProtocolInfo = ProtocolInfo{Features: []iproto.Feature{}} + conn := test_helpers.ConnectWithValidation(t, testDialer, opts) defer conn.Close() watcher, err := conn.NewWatcher(key, func(event WatchEvent) {}) require.Nilf(t, watcher, "watcher must not be created") require.NotNilf(t, err, "an error is expected") - expected := "the feature IPROTO_FEATURE_WATCHERS must be required by " + - "connection options to create a watcher" + expected := "the feature IPROTO_FEATURE_WATCHERS must be supported by " + + "connection to create a watcher" require.Equal(t, expected, err.Error()) } @@ -3762,11 +3637,13 @@ func TestConnection_NewWatcher_reconnect(t *testing.T) { const key = "TestConnection_NewWatcher_reconnect" const server = "127.0.0.1:3014" + testDialer := dialer + testDialer.Address = server + inst, err := test_helpers.StartTarantool(test_helpers.StartOpts{ + Dialer: testDialer, InitScript: "config.lua", Listen: server, - User: opts.User, - Pass: opts.Pass, WaitStart: 100 * time.Millisecond, ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, @@ -3779,10 +3656,8 @@ func TestConnection_NewWatcher_reconnect(t *testing.T) { reconnectOpts := opts reconnectOpts.Reconnect = 100 * time.Millisecond reconnectOpts.MaxReconnects = 10 - reconnectOpts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - conn := test_helpers.ConnectWithValidation(t, server, reconnectOpts) + + conn := test_helpers.ConnectWithValidation(t, testDialer, reconnectOpts) defer conn.Close() events := make(chan WatchEvent) @@ -3816,11 +3691,7 @@ func TestBroadcastRequest(t *testing.T) { const key = "TestBroadcastRequest" const value = "bar" - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - conn := test_helpers.ConnectWithValidation(t, server, connOpts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() resp, err := conn.Do(NewBroadcastRequest(key).Value(value)).Get() @@ -3866,11 +3737,7 @@ func TestBroadcastRequest_multi(t *testing.T) { const key = "TestBroadcastRequest_multi" - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - conn := test_helpers.ConnectWithValidation(t, server, connOpts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() events := make(chan WatchEvent) @@ -3914,11 +3781,7 @@ func TestConnection_NewWatcher_multiOnKey(t *testing.T) { const key = "TestConnection_NewWatcher_multiOnKey" const value = "bar" - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - conn := test_helpers.ConnectWithValidation(t, server, connOpts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() events := []chan WatchEvent{ @@ -3977,11 +3840,7 @@ func TestWatcher_Unregister(t *testing.T) { const key = "TestWatcher_Unregister" const value = "bar" - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - conn := test_helpers.ConnectWithValidation(t, server, connOpts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() events := make(chan WatchEvent) @@ -4013,11 +3872,8 @@ func TestConnection_NewWatcher_concurrent(t *testing.T) { const testConcurrency = 1000 const key = "TestConnection_NewWatcher_concurrent" - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - conn := test_helpers.ConnectWithValidation(t, server, connOpts) + + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() var wg sync.WaitGroup @@ -4058,11 +3914,8 @@ func TestWatcher_Unregister_concurrent(t *testing.T) { const testConcurrency = 1000 const key = "TestWatcher_Unregister_concurrent" - connOpts := opts.Clone() - connOpts.RequiredProtocolInfo.Features = []iproto.Feature{ - iproto.IPROTO_FEATURE_WATCHERS, - } - conn := test_helpers.ConnectWithValidation(t, server, connOpts) + + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() watcher, err := conn.NewWatcher(key, func(event WatchEvent) {}) @@ -4083,7 +3936,7 @@ func TestWatcher_Unregister_concurrent(t *testing.T) { } func TestConnect_schema_update(t *testing.T) { - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() for i := 0; i < 100; i++ { @@ -4091,7 +3944,7 @@ func TestConnect_schema_update(t *testing.T) { ctx, cancel := test_helpers.GetConnectContext() defer cancel() - if conn, err := Connect(ctx, server, opts); err != nil { + if conn, err := Connect(ctx, dialer, opts); err != nil { if err.Error() != "concurrent schema update" { t.Errorf("unexpected error: %s", err) } @@ -4110,8 +3963,6 @@ func TestConnect_schema_update(t *testing.T) { func TestConnect_context_cancel(t *testing.T) { var connLongReconnectOpts = Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", Reconnect: time.Second, MaxReconnects: 100, } @@ -4122,7 +3973,7 @@ func TestConnect_context_cancel(t *testing.T) { var err error cancel() - conn, err = Connect(ctx, server, connLongReconnectOpts) + conn, err = Connect(ctx, dialer, connLongReconnectOpts) if conn != nil || err == nil { t.Fatalf("Connection was created after cancel") @@ -4142,7 +3993,7 @@ func runTestMain(m *testing.M) int { // Tarantool supports streams and interactive transactions since version 2.10.0 isStreamUnsupported, err := test_helpers.IsTarantoolVersionLess(2, 10, 0) if err != nil { - log.Fatalf("Could not check the Tarantool version") + log.Fatalf("Could not check the Tarantool version: %s", err) } startOpts.MemtxUseMvccEngine = !isStreamUnsupported diff --git a/test_helpers/main.go b/test_helpers/main.go index cc806a7d2..d81ce3d1b 100644 --- a/test_helpers/main.go +++ b/test_helpers/main.go @@ -39,18 +39,6 @@ type StartOpts struct { // https://www.tarantool.io/en/doc/latest/reference/configuration/#cfg-basic-listen Listen string - // ClientServer changes a host to connect to test startup of a Tarantool - // instance. By default, it uses Listen value as the host for the connection. - ClientServer string - - // ClientTransport changes Opts.Transport for a connection that checks startup - // of a Tarantool instance. - ClientTransport string - - // ClientSsl changes Opts.Ssl for a connection that checks startup of - // a Tarantool instance. - ClientSsl tarantool.SslOpts - // WorkDir is box.cfg work_dir parameter for a Tarantool instance: // a folder to store data files. If not specified, helpers create a // new temporary directory. @@ -62,13 +50,6 @@ type StartOpts struct { // copied to the working directory. SslCertsDir string - // User is a username used to connect to tarantool. - // All required grants must be given in InitScript. - User string - - // Pass is a password for specified User. - Pass string - // WaitStart is a time to wait before starting to ping tarantool. WaitStart time.Duration @@ -82,6 +63,9 @@ type StartOpts struct { // MemtxUseMvccEngine is flag to enable transactional // manager if set to true. MemtxUseMvccEngine bool + + // Dialer to check that connection established. + Dialer tarantool.Dialer } // TarantoolInstance is a data for instance graceful shutdown and cleanup. @@ -91,16 +75,19 @@ type TarantoolInstance struct { // Options for restarting a tarantool instance. Opts StartOpts + + // Dialer to check that connection established. + Dialer tarantool.Dialer } -func isReady(server string, opts *tarantool.Opts) error { +func isReady(dialer tarantool.Dialer, opts *tarantool.Opts) error { var err error var conn *tarantool.Connection var resp *tarantool.Response ctx, cancel := GetConnectContext() defer cancel() - conn, err = tarantool.Connect(ctx, server, *opts) + conn, err = tarantool.Connect(ctx, dialer, *opts) if err != nil { return err } @@ -199,6 +186,8 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) { var dir string var err error + inst.Dialer = startOpts.Dialer + if startOpts.WorkDir == "" { // Create work_dir for a new instance. // TO DO: replace with `os.MkdirTemp` when we drop support of @@ -255,24 +244,13 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) { time.Sleep(startOpts.WaitStart) opts := tarantool.Opts{ - Auth: startOpts.Auth, Timeout: 500 * time.Millisecond, - User: startOpts.User, - Pass: startOpts.Pass, SkipSchema: true, - Transport: startOpts.ClientTransport, - Ssl: startOpts.ClientSsl, } var i int - var server string - if startOpts.ClientServer != "" { - server = startOpts.ClientServer - } else { - server = startOpts.Listen - } for i = 0; i <= startOpts.ConnectRetry; i++ { - err = isReady(server, &opts) + err = isReady(inst.Dialer, &opts) // Both connect and ping is ok. if err == nil { @@ -336,6 +314,9 @@ func copyDirectoryFiles(scrDir, dest string) error { return err } for _, entry := range entries { + if entry.IsDir() { + continue + } sourcePath := filepath.Join(scrDir, entry.Name()) destPath := filepath.Join(dest, entry.Name()) _, err := os.Stat(sourcePath) diff --git a/test_helpers/pool_helper.go b/test_helpers/pool_helper.go index b2340ccb8..fb59418d5 100644 --- a/test_helpers/pool_helper.go +++ b/test_helpers/pool_helper.go @@ -52,9 +52,9 @@ func CheckPoolStatuses(args interface{}) error { checkArgs.ExpectedPoolStatus, connected) } - poolInfo := checkArgs.ConnPool.GetPoolInfo() + poolInfo := checkArgs.ConnPool.GetInfo() for _, server := range checkArgs.Servers { - status := poolInfo[server] != nil && poolInfo[server].ConnectedNow + status := poolInfo[server].ConnectedNow if checkArgs.ExpectedStatuses[server] != status { return fmt.Errorf( "incorrect conn status: addr %s expected status %t actual status %t", @@ -106,7 +106,7 @@ func ProcessListenOnInstance(args interface{}) error { equal := reflect.DeepEqual(actualPorts, listenArgs.ExpectedPorts) if !equal { return fmt.Errorf("expected ports: %v, actual ports: %v", - actualPorts, listenArgs.ExpectedPorts) + listenArgs.ExpectedPorts, actualPorts) } return nil @@ -131,11 +131,11 @@ func Retry(f func(interface{}) error, args interface{}, count int, timeout time. return err } -func InsertOnInstance(ctx context.Context, server string, connOpts tarantool.Opts, +func InsertOnInstance(ctx context.Context, dialer tarantool.Dialer, connOpts tarantool.Opts, space interface{}, tuple interface{}) error { - conn, err := tarantool.Connect(ctx, server, connOpts) + conn, err := tarantool.Connect(ctx, dialer, connOpts) if err != nil { - return fmt.Errorf("fail to connect to %s: %s", server, err.Error()) + return fmt.Errorf("fail to connect: %s", err.Error()) } if conn == nil { return fmt.Errorf("conn is nil after Connect") @@ -169,22 +169,25 @@ func InsertOnInstance(ctx context.Context, server string, connOpts tarantool.Opt return nil } -func InsertOnInstances(servers []string, connOpts tarantool.Opts, space interface{}, +func InsertOnInstances( + dialers []tarantool.Dialer, + connOpts tarantool.Opts, + space interface{}, tuple interface{}) error { - serversNumber := len(servers) + serversNumber := len(dialers) roles := make([]bool, serversNumber) for i := 0; i < serversNumber; i++ { roles[i] = false } - err := SetClusterRO(servers, connOpts, roles) + err := SetClusterRO(dialers, connOpts, roles) if err != nil { return fmt.Errorf("fail to set roles for cluster: %s", err.Error()) } - for _, server := range servers { + for _, dialer := range dialers { ctx, cancel := GetConnectContext() - err := InsertOnInstance(ctx, server, connOpts, space, tuple) + err := InsertOnInstance(ctx, dialer, connOpts, space, tuple) cancel() if err != nil { return err @@ -194,9 +197,9 @@ func InsertOnInstances(servers []string, connOpts tarantool.Opts, space interfac return nil } -func SetInstanceRO(ctx context.Context, server string, connOpts tarantool.Opts, +func SetInstanceRO(ctx context.Context, dialer tarantool.Dialer, connOpts tarantool.Opts, isReplica bool) error { - conn, err := tarantool.Connect(ctx, server, connOpts) + conn, err := tarantool.Connect(ctx, dialer, connOpts) if err != nil { return err } @@ -212,14 +215,15 @@ func SetInstanceRO(ctx context.Context, server string, connOpts tarantool.Opts, return nil } -func SetClusterRO(servers []string, connOpts tarantool.Opts, roles []bool) error { - if len(servers) != len(roles) { +func SetClusterRO(dialers []tarantool.Dialer, connOpts tarantool.Opts, + roles []bool) error { + if len(dialers) != len(roles) { return fmt.Errorf("number of servers should be equal to number of roles") } - for i, server := range servers { + for i, dialer := range dialers { ctx, cancel := GetConnectContext() - err := SetInstanceRO(ctx, server, connOpts, roles[i]) + err := SetInstanceRO(ctx, dialer, connOpts, roles[i]) cancel() if err != nil { return err @@ -229,23 +233,10 @@ func SetClusterRO(servers []string, connOpts tarantool.Opts, roles []bool) error return nil } -func StartTarantoolInstances(servers []string, workDirs []string, - opts StartOpts) ([]TarantoolInstance, error) { - isUserWorkDirs := (workDirs != nil) - if isUserWorkDirs && (len(servers) != len(workDirs)) { - return nil, fmt.Errorf("number of servers should be equal to number of workDirs") - } - - instances := make([]TarantoolInstance, 0, len(servers)) - - for i, server := range servers { - opts.Listen = server - if isUserWorkDirs { - opts.WorkDir = workDirs[i] - } else { - opts.WorkDir = "" - } +func StartTarantoolInstances(instsOpts []StartOpts) ([]TarantoolInstance, error) { + instances := make([]TarantoolInstance, 0, len(instsOpts)) + for _, opts := range instsOpts { instance, err := StartTarantool(opts) if err != nil { StopTarantoolInstances(instances) diff --git a/test_helpers/utils.go b/test_helpers/utils.go index 52c078c90..e962dc619 100644 --- a/test_helpers/utils.go +++ b/test_helpers/utils.go @@ -13,13 +13,13 @@ import ( // It returns a valid connection if it is successful, otherwise finishes a test // with an error. func ConnectWithValidation(t testing.TB, - server string, + dialer tarantool.Dialer, opts tarantool.Opts) *tarantool.Connection { t.Helper() ctx, cancel := GetConnectContext() defer cancel() - conn, err := tarantool.Connect(ctx, server, opts) + conn, err := tarantool.Connect(ctx, dialer, opts) if err != nil { t.Fatalf("Failed to connect: %s", err.Error()) } @@ -67,7 +67,7 @@ func SkipIfSQLUnsupported(t testing.TB) { // Tarantool supports SQL since version 2.0.0 isLess, err := IsTarantoolVersionLess(2, 0, 0) if err != nil { - t.Fatalf("Could not check the Tarantool version") + t.Fatalf("Could not check the Tarantool version: %s", err) } if isLess { t.Skip() @@ -80,7 +80,7 @@ func SkipIfLess(t *testing.T, reason string, major, minor, patch uint64) { isLess, err := IsTarantoolVersionLess(major, minor, patch) if err != nil { - t.Fatalf("Could not check the Tarantool version") + t.Fatalf("Could not check the Tarantool version: %s", err) } if isLess { @@ -95,7 +95,7 @@ func SkipIfGreaterOrEqual(t *testing.T, reason string, major, minor, patch uint6 isLess, err := IsTarantoolVersionLess(major, minor, patch) if err != nil { - t.Fatalf("Could not check the Tarantool version") + t.Fatalf("Could not check the Tarantool version: %s", err) } if !isLess { diff --git a/uuid/example_test.go b/uuid/example_test.go index 08bd64aae..ba90ea905 100644 --- a/uuid/example_test.go +++ b/uuid/example_test.go @@ -19,16 +19,21 @@ import ( _ "github.com/tarantool/go-tarantool/v2/uuid" ) +var exampleOpts = tarantool.Opts{ + Timeout: 5 * time.Second, +} + // Example demonstrates how to use tuples with UUID. To enable UUID support // in msgpack with google/uuid (https://github.com/google/uuid), import // tarantool/uuid submodule. func Example() { - opts := tarantool.Opts{ - User: "test", - Pass: "test", + dialer := tarantool.NetDialer{ + Address: "127.0.0.1:3013", + User: "test", + Password: "test", } ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - client, err := tarantool.Connect(ctx, "127.0.0.1:3013", opts) + client, err := tarantool.Connect(ctx, dialer, exampleOpts) cancel() if err != nil { log.Fatalf("Failed to connect: %s", err.Error()) diff --git a/uuid/uuid_test.go b/uuid/uuid_test.go index 94baaa6ea..fdbf0cd82 100644 --- a/uuid/uuid_test.go +++ b/uuid/uuid_test.go @@ -23,8 +23,11 @@ var isUUIDSupported = false var server = "127.0.0.1:3013" var opts = Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", +} +var dialer = NetDialer{ + Address: server, + User: "test", + Password: "test", } var space = "testUUID" @@ -73,7 +76,7 @@ func TestSelect(t *testing.T) { t.Skip("Skipping test for Tarantool without UUID support in msgpack") } - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() id, uuidErr := uuid.Parse("c8f0fa1f-da29-438c-a040-393f1126ad39") @@ -113,7 +116,7 @@ func TestReplace(t *testing.T) { t.Skip("Skipping test for Tarantool without UUID support in msgpack") } - conn := test_helpers.ConnectWithValidation(t, server, opts) + conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() id, uuidErr := uuid.Parse("64d22e4d-ac92-4a23-899a-e59f34af5479") @@ -166,10 +169,9 @@ func runTestMain(m *testing.M) int { } inst, err := test_helpers.StartTarantool(test_helpers.StartOpts{ + Dialer: dialer, InitScript: "config.lua", Listen: server, - User: opts.User, - Pass: opts.Pass, WaitStart: 100 * time.Millisecond, ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, From 4d3cd20f999a32eda1579d82c48c870ea78b700c Mon Sep 17 00:00:00 2001 From: Albert Skalt Date: Thu, 16 Nov 2023 10:37:55 +0300 Subject: [PATCH 2/4] api: add the ability to connect via socket fd This patch introduces `FdDialer`, which connects to Tarantool using an existing socket file descriptor. `FdDialer` is not authenticated when creating a connection. Part of #321 --- .gitignore | 1 + dial.go | 56 +++++++++++++++++++++++++++ dial_test.go | 79 +++++++++++++++++++++++++++++++++++++ example_test.go | 32 +++++++++++++++ tarantool_test.go | 84 ++++++++++++++++++++++++++++++++++++++++ testdata/sidecar/main.go | 37 ++++++++++++++++++ 6 files changed, 289 insertions(+) create mode 100644 testdata/sidecar/main.go diff --git a/.gitignore b/.gitignore index fcd3c3236..c9f687eb1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ work_dir* .rocks bench* +testdata/sidecar/main diff --git a/dial.go b/dial.go index 6b75adafa..eae8e1283 100644 --- a/dial.go +++ b/dial.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net" + "os" "strings" "time" @@ -252,6 +253,61 @@ func (d OpenSslDialer) Dial(ctx context.Context, opts DialOpts) (Conn, error) { return conn, nil } +// FdDialer allows to use an existing socket fd for connection. +type FdDialer struct { + // Fd is a socket file descrpitor. + Fd uintptr + // RequiredProtocol contains minimal protocol version and + // list of protocol features that should be supported by + // Tarantool server. By default, there are no restrictions. + RequiredProtocolInfo ProtocolInfo +} + +type fdAddr struct { + Fd uintptr +} + +func (a fdAddr) Network() string { + return "fd" +} + +func (a fdAddr) String() string { + return fmt.Sprintf("fd://%d", a.Fd) +} + +type fdConn struct { + net.Conn + Addr fdAddr +} + +func (c *fdConn) RemoteAddr() net.Addr { + return c.Addr +} + +// Dial makes FdDialer satisfy the Dialer interface. +func (d FdDialer) Dial(ctx context.Context, opts DialOpts) (Conn, error) { + file := os.NewFile(d.Fd, "") + c, err := net.FileConn(file) + if err != nil { + return nil, fmt.Errorf("failed to dial: %w", err) + } + + conn := new(tntConn) + conn.net = &fdConn{Conn: c, Addr: fdAddr{Fd: d.Fd}} + + dc := &deadlineIO{to: opts.IoTimeout, c: conn.net} + conn.reader = bufio.NewReaderSize(dc, bufSize) + conn.writer = bufio.NewWriterSize(dc, bufSize) + + _, err = rawDial(conn, d.RequiredProtocolInfo) + if err != nil { + conn.net.Close() + return nil, err + } + + return conn, nil +} + // Addr makes tntConn satisfy the Conn interface. func (c *tntConn) Addr() net.Addr { return c.net.RemoteAddr() diff --git a/dial_test.go b/dial_test.go index c8cf1c778..ac8cab2aa 100644 --- a/dial_test.go +++ b/dial_test.go @@ -442,6 +442,7 @@ type testDialOpts struct { isIdUnsupported bool isPapSha256Auth bool isErrAuth bool + isEmptyAuth bool } type dialServerActual struct { @@ -484,6 +485,8 @@ func testDialAccept(opts testDialOpts, l net.Listener) chan dialServerActual { authRequestExpected := authRequestExpectedChapSha1 if opts.isPapSha256Auth { authRequestExpected = authRequestExpectedPapSha256 + } else if opts.isEmptyAuth { + authRequestExpected = []byte{} } authRequestActual := make([]byte, len(authRequestExpected)) client.Read(authRequestActual) @@ -526,6 +529,8 @@ func testDialer(t *testing.T, l net.Listener, dialer tarantool.Dialer, authRequestExpected := authRequestExpectedChapSha1 if opts.isPapSha256Auth { authRequestExpected = authRequestExpectedPapSha256 + } else if opts.isEmptyAuth { + authRequestExpected = []byte{} } require.Equal(t, authRequestExpected, actual.AuthRequest) conn.Close() @@ -779,3 +784,77 @@ func TestOpenSslDialer_Dial_ctx_cancel(t *testing.T) { } require.Error(t, err) } + +func TestFdDialer_Dial(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + defer l.Close() + addr := l.Addr().String() + + cases := []testDialOpts{ + { + name: "all is ok", + expectedProtocolInfo: idResponseTyped.Clone(), + isEmptyAuth: true, + }, + { + name: "id request unsupported", + expectedProtocolInfo: tarantool.ProtocolInfo{}, + isIdUnsupported: true, + isEmptyAuth: true, + }, + { + name: "greeting response error", + wantErr: true, + expectedErr: "failed to read greeting", + isErrGreeting: true, + }, + { + name: "id response error", + wantErr: true, + expectedErr: "failed to identify", + isErrId: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + sock, err := net.Dial("tcp", addr) + require.NoError(t, err) + f, err := sock.(*net.TCPConn).File() + require.NoError(t, err) + dialer := tarantool.FdDialer{ + Fd: f.Fd(), + } + testDialer(t, l, dialer, tc) + }) + } +} + +func TestFdDialer_Dial_requirements(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + defer l.Close() + addr := l.Addr().String() + + sock, err := net.Dial("tcp", addr) + require.NoError(t, err) + f, err := sock.(*net.TCPConn).File() + require.NoError(t, err) + dialer := tarantool.FdDialer{ + Fd: f.Fd(), + RequiredProtocolInfo: tarantool.ProtocolInfo{ + Features: []iproto.Feature{42}, + }, + } + + testDialAccept(testDialOpts{}, l) + ctx, cancel := test_helpers.GetConnectContext() + defer cancel() + conn, err := dialer.Dial(ctx, tarantool.DialOpts{}) + if err == nil { + conn.Close() + } + require.Error(t, err) + require.Contains(t, err.Error(), "invalid server protocol") +} diff --git a/example_test.go b/example_test.go index 00cd00467..6500e18f4 100644 --- a/example_test.go +++ b/example_test.go @@ -3,6 +3,7 @@ package tarantool_test import ( "context" "fmt" + "net" "time" "github.com/tarantool/go-iproto" @@ -1350,3 +1351,34 @@ func ExampleWatchOnceRequest() { fmt.Println(resp.Data) } } + +// This example demonstrates how to use an existing socket file descriptor +// to establish a connection with Tarantool. This can be useful if the socket fd +// was inherited from the Tarantool process itself. +// For details, please see TestFdDialer in tarantool_test.go. +func ExampleFdDialer() { + addr := dialer.Address + c, err := net.Dial("tcp", addr) + if err != nil { + fmt.Printf("can't establish connection: %v\n", err) + return + } + f, err := c.(*net.TCPConn).File() + if err != nil { + fmt.Printf("unexpected error: %v\n", err) + return + } + dialer := tarantool.FdDialer{ + Fd: f.Fd(), + } + // Use an existing socket fd to create connection with Tarantool. + conn, err := tarantool.Connect(context.Background(), dialer, opts) + if err != nil { + fmt.Printf("connect error: %v\n", err) + return + } + resp, err := conn.Do(tarantool.NewPingRequest()).Get() + fmt.Println(resp.Code, err) + // Output: + // 0 +} diff --git a/tarantool_test.go b/tarantool_test.go index e54c6a46d..4d3f193f1 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -8,6 +8,8 @@ import ( "log" "math" "os" + "os/exec" + "path/filepath" "reflect" "runtime" "strings" @@ -77,6 +79,7 @@ func (m *Member) DecodeMsgpack(d *msgpack.Decoder) error { } var server = "127.0.0.1:3013" +var fdDialerTestServer = "127.0.0.1:3014" var spaceNo = uint32(617) var spaceName = "test" var indexNo = uint32(0) @@ -3984,6 +3987,87 @@ func TestConnect_context_cancel(t *testing.T) { } } +func buildSidecar(dir string) error { + goPath, err := exec.LookPath("go") + if err != nil { + return err + } + cmd := exec.Command(goPath, "build", "main.go") + cmd.Dir = filepath.Join(dir, "testdata", "sidecar") + return cmd.Run() +} + +func TestFdDialer(t *testing.T) { + isLess, err := test_helpers.IsTarantoolVersionLess(3, 0, 0) + if err != nil || isLess { + t.Skip("box.session.new present in Tarantool since version 3.0") + } + + wd, err := os.Getwd() + require.NoError(t, err) + + err = buildSidecar(wd) + require.NoErrorf(t, err, "failed to build sidecar: %v", err) + + instOpts := startOpts + instOpts.Listen = fdDialerTestServer + instOpts.Dialer = NetDialer{ + Address: fdDialerTestServer, + User: "test", + Password: "test", + } + + inst, err := test_helpers.StartTarantool(instOpts) + require.NoError(t, err) + defer test_helpers.StopTarantoolWithCleanup(inst) + + conn := test_helpers.ConnectWithValidation(t, dialer, opts) + defer conn.Close() + + sidecarExe := filepath.Join(wd, "testdata", "sidecar", "main") + + evalBody := fmt.Sprintf(` + local socket = require('socket') + local popen = require('popen') + local os = require('os') + local s1, s2 = socket.socketpair('AF_UNIX', 'SOCK_STREAM', 0) + + --[[ Tell sidecar which fd use to connect. --]] + os.setenv('SOCKET_FD', tostring(s2:fd())) + + box.session.new({ + type = 'binary', + fd = s1:fd(), + user = 'test', + }) + s1:detach() + + local ph, err = popen.new({'%s'}, { + stdout = popen.opts.PIPE, + stderr = popen.opts.PIPE, + inherit_fds = {s2:fd()}, + }) + + if err ~= nil then + return 1, err + end + + ph:wait() + + local status_code = ph:info().status.exit_code + local stderr = ph:read({stderr=true}):rstrip() + local stdout = ph:read({stdout=true}):rstrip() + return status_code, stderr, stdout + `, sidecarExe) + + var resp []interface{} + err = conn.EvalTyped(evalBody, []interface{}{}, &resp) + require.NoError(t, err) + require.Equal(t, "", resp[1], resp[1]) + require.Equal(t, "", resp[2], resp[2]) + require.Equal(t, int8(0), resp[0]) +} + // runTestMain is a body of TestMain function // (see https://pkg.go.dev/testing#hdr-Main). // Using defer + os.Exit is not works so TestMain body diff --git a/testdata/sidecar/main.go b/testdata/sidecar/main.go new file mode 100644 index 000000000..971b8694c --- /dev/null +++ b/testdata/sidecar/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "context" + "os" + "strconv" + + "github.com/tarantool/go-tarantool/v2" +) + +func main() { + fd, err := strconv.Atoi(os.Getenv("SOCKET_FD")) + if err != nil { + panic(err) + } + dialer := tarantool.FdDialer{ + Fd: uintptr(fd), + } + conn, err := tarantool.Connect(context.Background(), dialer, tarantool.Opts{}) + if err != nil { + panic(err) + } + if _, err := conn.Do(tarantool.NewPingRequest()).Get(); err != nil { + panic(err) + } + // Insert new tuple. + if _, err := conn.Do(tarantool.NewInsertRequest("test"). + Tuple([]interface{}{239})).Get(); err != nil { + panic(err) + } + // Delete inserted tuple. + if _, err := conn.Do(tarantool.NewDeleteRequest("test"). + Index("primary"). + Key([]interface{}{239})).Get(); err != nil { + panic(err) + } +} From 321d7b9ee5b3d3907e90653bc0ecbcc741324cf2 Mon Sep 17 00:00:00 2001 From: Albert Skalt Date: Fri, 17 Nov 2023 15:11:58 +0300 Subject: [PATCH 3/4] changelog: update according to the changes Part of #321 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e585d8e37..5eb1b0968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. version >= 3.0.0-alpha1 (#338). It allows to use space and index names in requests instead of their IDs. - `GetSchema` function to get the actual schema (#7) +- Support connection via an existing socket fd (#321) ### Changed @@ -55,6 +56,17 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. - Change `OverrideSchema(*Schema)` to `SetSchema(Schema)` (#7) - Change values, stored by pointers in the `Schema`, `Space`, `Index` structs, to be stored by their values (#7) +- Make `Dialer` mandatory for creation a single connection / connection pool (#321) +- Remove `Connection.RemoteAddr()`, `Connection.LocalAddr()`. + Add `Addr()` function instead (#321) +- Remove `Connection.ClientProtocolInfo`, `Connection.ServerProtocolInfo`. + Add `ProtocolInfo()` function, which returns the server protocol info (#321) +- `NewWatcher` checks the actual features of the server, rather than relying + on the features provided by the user during connection creation (#321) +- `pool.NewWatcher` does not create watchers for connections that do not support + it (#321) +- Rename `pool.GetPoolInfo` to `pool.GetInfo`. Change return type to + `map[string]ConnectionInfo` (#321) ### Deprecated From 4cc6677d97d333426af16a30028f44ffc1550b65 Mon Sep 17 00:00:00 2001 From: Albert Skalt Date: Fri, 24 Nov 2023 20:50:49 +0300 Subject: [PATCH 4/4] doc: update according to the changes Closes #321 --- README.md | 67 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 6a7a51f0c..b1d47ee84 100644 --- a/README.md +++ b/README.md @@ -109,16 +109,22 @@ import ( "context" "fmt" "time" - + "github.com/tarantool/go-tarantool/v2" ) func main() { - opts := tarantool.Opts{User: "guest"} - ctx, cancel := context.WithTimeout(context.Background(), + ctx, cancel := context.WithTimeout(context.Background(), 500 * time.Millisecond) defer cancel() - conn, err := tarantool.Connect(ctx, "127.0.0.1:3301", opts) + dialer := tarantool.NetDialer { + Address: "127.0.0.1:3301", + User: "guest", + } + opts := tarantool.Opts{ + Timeout: time.Second, + } + conn, err := tarantool.Connect(ctx, dialer, opts) if err != nil { fmt.Println("Connection refused:", err) } @@ -135,27 +141,30 @@ func main() { **Observation 1:** The line "`github.com/tarantool/go-tarantool/v2`" in the `import(...)` section brings in all Tarantool-related functions and structures. -**Observation 2:** The line starting with "`Opts :=`" sets up the options for +**Observation 2:** The line starting with "`dialer :=`" creates dialer for +`Connect()`. This structure contains fields required to establish a connection. + +**Observation 3:** The line starting with "`opts :=`" sets up the options for `Connect()`. In this example, the structure contains only a single value, the -username. The structure may also contain other settings, see more in +timeout. The structure may also contain other settings, see more in [documentation][godoc-opts-url] for the "`Opts`" structure. -**Observation 3:** The line containing "`tarantool.Connect`" is essential for +**Observation 4:** The line containing "`tarantool.Connect`" is essential for starting a session. There are three parameters: * a context, -* a string with `host:port` format, +* the dialer that was set up earlier, * the option structure that was set up earlier. -There will be only one attempt to connect. If multiple attempts needed, -"`tarantool.Connect`" could be placed inside the loop with some timeout -between each try. Example could be found in the [example_test](./example_test.go), +There will be only one attempt to connect. If multiple attempts needed, +"`tarantool.Connect`" could be placed inside the loop with some timeout +between each try. Example could be found in the [example_test](./example_test.go), name - `ExampleConnect_reconnects`. -**Observation 4:** The `err` structure will be `nil` if there is no error, +**Observation 5:** The `err` structure will be `nil` if there is no error, otherwise it will have a description which can be retrieved with `err.Error()`. -**Observation 5:** The `Insert` request, like almost all requests, is preceded +**Observation 6:** The `Insert` request, like almost all requests, is preceded by the method `Do` of object `conn` which is the object that was returned by `Connect()`. @@ -182,11 +191,16 @@ The subpackage has been deleted. You could use `pool` instead. * The `connection_pool` subpackage has been renamed to `pool`. * The type `PoolOpts` has been renamed to `Opts`. -* `pool.Connect` now accepts context as first argument, which user may cancel - in process. If it is canceled in progress, an error will be returned. +* `pool.Connect` now accepts context as first argument, which user may cancel + in process. If it is canceled in progress, an error will be returned. All created connections will be closed. -* `pool.Add` now accepts context as first argument, which user may cancel in +* `pool.Add` now accepts context as first argument, which user may cancel in process. +* Now you need to pass `map[string]Dialer` to the `pool.Connect` as the second + argument, instead of a list of addresses. Each dialer is associated with a + unique string ID, which allows them to be distinguished. +* `pool.GetPoolInfo` has been renamed to `pool.GetInfo`. Return type has been changed + to `map[string]ConnectionInfo`. #### crud package @@ -235,7 +249,7 @@ IPROTO constants have been moved to a separate package [go-iproto](https://githu * `Op` struct for update operations made private. * Removed `OpSplice` struct. * `Operations.Splice` method now accepts 5 arguments instead of 3. -* Requests `Update`, `UpdateAsync`, `UpdateTyped`, `Upsert`, `UpsertAsync` no +* Requests `Update`, `UpdateAsync`, `UpdateTyped`, `Upsert`, `UpsertAsync` no longer accept `ops` argument (operations) as an `interface{}`. `*Operations` needs to be passed instead. * `UpdateRequest` and `UpsertRequest` structs no longer accept `interface{}` @@ -243,15 +257,20 @@ for an `ops` field. `*Operations` needs to be used instead. #### Connect function -`connection.Connect` no longer return non-working connection objects. This function +`connection.Connect` no longer return non-working connection objects. This function now does not attempt to reconnect and tries to establish a connection only once. -Function might be canceled via context. Context accepted as first argument, +Function might be canceled via context. Context accepted as first argument, and user may cancel it in process. +Now you need to pass `Dialer` as the second argument instead of URI. +If you were using a non-SSL connection, you need to create `NetDialer`. +For SSL-enabled connections, use `OpenSslDialer`. Please note that the options +for creating a connection are now stored in corresponding `Dialer`, not in `Opts`. + #### Connection schema -* Removed `Schema` field from the `Connection` struct. Instead, new -`GetSchema(Connector)` function was added to get the actual connection +* Removed `Schema` field from the `Connection` struct. Instead, new +`GetSchema(Connector)` function was added to get the actual connection schema on demand. * `OverrideSchema(*Schema)` method replaced with the `SetSchema(Schema)`. @@ -262,9 +281,9 @@ schema on demand. #### Schema changes -* `ResolveSpaceIndex` function for `SchemaResolver` interface split into two: -`ResolveSpace` and `ResolveIndex`. `NamesUseSupported` function added into the -interface to get information if the usage of space and index names in requests +* `ResolveSpaceIndex` function for `SchemaResolver` interface split into two: +`ResolveSpace` and `ResolveIndex`. `NamesUseSupported` function added into the +interface to get information if the usage of space and index names in requests is supported. * `Schema` structure no longer implements `SchemaResolver` interface. * `Spaces` and `SpacesById` fields of the `Schema` struct store spaces by value.