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/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 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. 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..eae8e1283 100644 --- a/dial.go +++ b/dial.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net" + "os" "strings" "time" @@ -15,10 +16,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 +43,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 +78,241 @@ 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 } -// 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) { +// 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 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 +} + +// 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() +} + // Read makes tntConn satisfy the Conn interface. func (c *tntConn) Read(p []byte) (int, error) { return c.reader.Read(p) @@ -177,16 +339,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 +349,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 +454,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..ac8cab2aa 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,539 @@ 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 + isEmptyAuth 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 + } else if opts.isEmptyAuth { + authRequestExpected = []byte{} + } + 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 + } else if opts.isEmptyAuth { + authRequestExpected = []byte{} + } + 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) +} + +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_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..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" @@ -20,10 +21,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 +32,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 +72,7 @@ func ExampleIntKey() { } func ExampleUintKey() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() const space = "test" @@ -95,7 +94,7 @@ func ExampleUintKey() { } func ExampleStringKey() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() const space = "teststring" @@ -120,7 +119,7 @@ func ExampleStringKey() { } func ExampleIntIntKey() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() const space = "testintint" @@ -146,7 +145,7 @@ func ExampleIntIntKey() { } func ExamplePingRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Ping a Tarantool instance to check connection. @@ -166,7 +165,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 +190,7 @@ func ExamplePingRequest_Context() { } func ExampleSelectRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() for i := 1111; i <= 1112; i++ { @@ -232,7 +231,7 @@ func ExampleSelectRequest() { } func ExampleSelectRequest_spaceAndIndexNames() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewSelectRequest(spaceName) @@ -247,7 +246,7 @@ func ExampleSelectRequest_spaceAndIndexNames() { } func ExampleInsertRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Insert a new tuple { 31, 1 }. @@ -289,7 +288,7 @@ func ExampleInsertRequest() { } func ExampleInsertRequest_spaceAndIndexNames() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewInsertRequest(spaceName) @@ -303,7 +302,7 @@ func ExampleInsertRequest_spaceAndIndexNames() { } func ExampleDeleteRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Insert a new tuple { 35, 1 }. @@ -346,7 +345,7 @@ func ExampleDeleteRequest() { } func ExampleDeleteRequest_spaceAndIndexNames() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewDeleteRequest(spaceName) @@ -361,7 +360,7 @@ func ExampleDeleteRequest_spaceAndIndexNames() { } func ExampleReplaceRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Insert a new tuple { 13, 1 }. @@ -420,7 +419,7 @@ func ExampleReplaceRequest() { } func ExampleReplaceRequest_spaceAndIndexNames() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewReplaceRequest(spaceName) @@ -434,7 +433,7 @@ func ExampleReplaceRequest_spaceAndIndexNames() { } func ExampleUpdateRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() for i := 1111; i <= 1112; i++ { @@ -465,7 +464,7 @@ func ExampleUpdateRequest() { } func ExampleUpdateRequest_spaceAndIndexNames() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewUpdateRequest(spaceName) @@ -480,7 +479,7 @@ func ExampleUpdateRequest_spaceAndIndexNames() { } func ExampleUpsertRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() var req tarantool.Request @@ -521,7 +520,7 @@ func ExampleUpsertRequest() { } func ExampleUpsertRequest_spaceAndIndexNames() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewUpsertRequest(spaceName) @@ -535,7 +534,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 +553,7 @@ func ExampleCallRequest() { } func ExampleEvalRequest() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() // Run raw Lua code. @@ -582,7 +581,7 @@ func ExampleExecuteRequest() { return } - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() req := tarantool.NewExecuteRequest( @@ -700,31 +699,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 +711,7 @@ func getTestTxnOpts() tarantool.Opts { }, } - return txnOpts + return txnDialer } func ExampleCommitRequest() { @@ -746,8 +725,8 @@ func ExampleCommitRequest() { return } - txnOpts := getTestTxnOpts() - conn := exampleConnect(txnOpts) + txnDialer := getTestTxnDialer() + conn := exampleConnect(txnDialer, opts) defer conn.Close() stream, _ := conn.NewStream() @@ -823,8 +802,8 @@ func ExampleRollbackRequest() { return } - txnOpts := getTestTxnOpts() - conn := exampleConnect(txnOpts) + txnDialer := getTestTxnDialer() + conn := exampleConnect(txnDialer, opts) defer conn.Close() stream, _ := conn.NewStream() @@ -900,8 +879,8 @@ func ExampleBeginRequest_TxnIsolation() { return } - txnOpts := getTestTxnOpts() - conn := exampleConnect(txnOpts) + txnDialer := getTestTxnDialer() + conn := exampleConnect(txnDialer, opts) defer conn.Close() stream, _ := conn.NewStream() @@ -969,7 +948,7 @@ func ExampleBeginRequest_TxnIsolation() { } func ExampleFuture_GetIterator() { - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() const timeout = 3 * time.Second @@ -1006,10 +985,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 +1008,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 +1026,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 +1047,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 +1072,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 +1086,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 +1135,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 +1162,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 +1209,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 +1253,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 +1296,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 +1339,7 @@ func ExampleWatchOnceRequest() { return } - conn := exampleConnect(opts) + conn := exampleConnect(dialer, opts) defer conn.Close() conn.Do(tarantool.NewBroadcastRequest(key).Value(value)).Get() @@ -1359,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/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..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" @@ -25,15 +27,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 @@ -72,14 +79,13 @@ 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) var indexName = "primary" var opts = Opts{ Timeout: 5 * time.Second, - User: "test", - Pass: "test", //Concurrency: 32, //RateLimit: 4*1024, } @@ -89,7 +95,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 +115,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 +142,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 +171,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 +192,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 +214,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 +246,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 +273,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 +303,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 +336,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 +372,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 +393,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 +421,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 +453,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 +492,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 +518,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 +553,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 +588,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 +603,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 +622,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 +646,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 +680,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 +699,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 +743,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 +782,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 +795,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 +824,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 +1164,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 +1318,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 +1464,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 +1495,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 +1524,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 +1626,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 +1723,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 +1812,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 +1825,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 +1889,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 +2039,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 +2078,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 +2175,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 +2466,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 +2569,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 +2587,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 +2610,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 +2634,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 +2653,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 +2673,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 +2697,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 +2713,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 +2727,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 +2743,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 +2785,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 +2824,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 +2855,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 +2892,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 +3007,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 +3128,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 +3229,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 +3248,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 +3269,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 +3281,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 +3317,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 +3351,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 +3369,41 @@ func TestClientIdRequestObjectWithPassedCanceledContext(t *testing.T) { func TestConnectionProtocolInfoUnsupported(t *testing.T) { test_helpers.SkipIfIdSupported(t) - conn := test_helpers.ConnectWithValidation(t, server, 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) + conn := test_helpers.ConnectWithValidation(t, dialer, 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 +3414,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 +3431,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 +3449,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 +3468,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 +3486,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 +3517,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 +3553,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 +3587,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 +3618,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 +3640,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 +3659,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 +3694,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 +3740,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 +3784,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 +3843,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 +3875,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 +3917,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 +3939,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 +3947,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 +3966,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 +3976,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") @@ -4133,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 @@ -4142,7 +4077,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/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) + } +} 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,