Skip to content

Commit

Permalink
pool: get instance status from WatchOnce request
Browse files Browse the repository at this point in the history
Starting from Tarantool version >= 3.0.0 `WatchOnce` requset is
supported. So we can get instance status using this request instead
of calling `box.info`.

This way user can add instances to the pool without the `execute` role.

Closes #380
  • Loading branch information
DerekBum committed Mar 5, 2024
1 parent c070b26 commit 31c67c6
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 2 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.

### Changed

- `execute` role is no longer required for adding instances to the pool for
Tarantool version >= 3.0.0 (#380)

### Fixed

- `ConnectionPool.Remove()` does not notify a `ConnectionHandler` after
Expand Down
3 changes: 3 additions & 0 deletions pool/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ box.once("init", function()
box.schema.user.create('test', { password = 'test' })
box.schema.user.grant('test', 'read,write,execute', 'universe')

box.schema.user.create('testNoExecute', { password = 'test' })
box.schema.user.grant('testNoExecute', 'read,write', 'universe')

local s = box.schema.space.create('testPool', {
id = 520,
if_not_exists = true,
Expand Down
17 changes: 15 additions & 2 deletions pool/connection_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,20 @@ func (p *ConnectionPool) DoInstance(req tarantool.Request, name string) *taranto
//

func (p *ConnectionPool) getConnectionRole(conn *tarantool.Connection) (Role, error) {
data, err := conn.Do(tarantool.NewCallRequest("box.info")).Get()
var (
roFieldName string
data []interface{}
err error
)

if isFeatureInSlice(iproto.IPROTO_FEATURE_WATCH_ONCE, conn.ProtocolInfo().Features) {
roFieldName = "is_ro"
data, err = conn.Do(tarantool.NewWatchOnceRequest("box.status")).Get()
} else {
roFieldName = "ro"
data, err = conn.Do(tarantool.NewCallRequest("box.info")).Get()
}

if err != nil {
return UnknownRole, err
}
Expand All @@ -1033,7 +1046,7 @@ func (p *ConnectionPool) getConnectionRole(conn *tarantool.Connection) (Role, er
return UnknownRole, ErrIncorrectStatus
}

replicaRole, ok := data[0].(map[interface{}]interface{})["ro"]
replicaRole, ok := data[0].(map[interface{}]interface{})[roFieldName]
if !ok {
return UnknownRole, ErrIncorrectResponse
}
Expand Down
84 changes: 84 additions & 0 deletions pool/connection_pool_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package pool_test

import (
"bytes"
"context"
"fmt"
"log"
Expand All @@ -22,6 +23,7 @@ import (
)

var user = "test"
var userNoExecute = "testNoExecute"
var pass = "test"
var spaceNo = uint32(520)
var spaceName = "testPool"
Expand Down Expand Up @@ -68,6 +70,18 @@ func makeInstance(server string, opts tarantool.Opts) pool.Instance {
}
}

func makeNoExecuteInstance(server string, opts tarantool.Opts) pool.Instance {
return pool.Instance{
Name: server,
Dialer: tarantool.NetDialer{
Address: server,
User: userNoExecute,
Password: pass,
},
Opts: opts,
}
}

func makeInstances(servers []string, opts tarantool.Opts) []pool.Instance {
var instances []pool.Instance
for _, server := range servers {
Expand Down Expand Up @@ -130,6 +144,76 @@ func TestConnSuccessfully(t *testing.T) {
require.Nil(t, err)
}

func TestConn_NoExecuteRole_Supported(t *testing.T) {
test_helpers.SkipIfWatchOnceUnsupported(t)

healthyServ := servers[0]

ctx, cancel := test_helpers.GetPoolConnectContext()
defer cancel()
connPool, err := pool.Connect(ctx,
[]pool.Instance{makeNoExecuteInstance(healthyServ, connOpts)})
require.Nilf(t, err, "failed to connect")
require.NotNilf(t, connPool, "conn is nil after Connect")

defer connPool.Close()

args := test_helpers.CheckStatusesArgs{
ConnPool: connPool,
Mode: pool.ANY,
Servers: []string{healthyServ},
ExpectedPoolStatus: true,
ExpectedStatuses: map[string]bool{
healthyServ: true,
},
}

err = test_helpers.CheckPoolStatuses(args)
require.Nil(t, err)
}

func TestConn_NoExecuteRole_Unsupported(t *testing.T) {
watchOnceUnsupported, err := test_helpers.IsTarantoolVersionLess(3, 0, 0)
require.NoError(t, err)
if !watchOnceUnsupported {
t.Skip("Skipping test for Tarantool with watch once support")
}

var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()

healthyServ := servers[0]

ctx, cancel := test_helpers.GetPoolConnectContext()
defer cancel()
connPool, err := pool.Connect(ctx,
[]pool.Instance{makeNoExecuteInstance(healthyServ, connOpts)})
require.Nilf(t, err, "failed to connect")
require.NotNilf(t, connPool, "conn is nil after Connect")

defer connPool.Close()

require.Contains(t, buf.String(),
fmt.Sprintf("connect to %s failed: Execute access to function "+
"'box.info' is denied for user '%s'", servers[0], userNoExecute))

args := test_helpers.CheckStatusesArgs{
ConnPool: connPool,
Mode: pool.ANY,
Servers: []string{healthyServ},
ExpectedPoolStatus: false,
ExpectedStatuses: map[string]bool{
healthyServ: false,
},
}

err = test_helpers.CheckPoolStatuses(args)
require.Nil(t, err)
}

func TestConnect_empty(t *testing.T) {
cases := []struct {
Name string
Expand Down

0 comments on commit 31c67c6

Please sign in to comment.