Skip to content

Commit 4b38d6d

Browse files
api: support iproto feature discovery
Since version 2.10.0 Tarantool supports feature discovery [1]. Client can send client protocol version and supported features and receive server protocol version and supported features information to tune its behavior. After this patch, the request will be sent on `dial`, before authentication is performed. Connector stores server info in connection internals. User can also set option RequiredProtocolVersion and RequiredProtocolFeatures to fast fail on connect if server does not provide some expected feature, similar to net.box opts [2]. It is not clear how connector should behave in case if client doesn't support a protocol feature or protocol version, see [3]. For now we decided not to check requirements on client side. Feature check iterates over lists to check if feature is enabled. It seems that iterating over a small list is way faster than building a map, see [4]. Benchmark tests show that this check is rather fast (0.5 ns for both client and server check on HP ProBook 440 G5) so it is not necessary to cache it in any way. Traces of IPROTO_FEATURE_GRACEFUL_SHUTDOWN flag and protocol version 4 could be found in Tarantool source code but they were removed in the following commits before the release and treated like they never existed. We also ignore them here too. See [5] for more info. In latest master commit new feature with code 4 and protocol version 4 were introduced [6]. 1. tarantool/tarantool#6253 2. https://www.tarantool.io/en/doc/latest/reference/reference_lua/net_box/#lua-function.net_box.new 3. tarantool/tarantool#7953 4. https://stackoverflow.com/a/52710077/11646599 5. tarantool/tarantool-python#262 6. tarantool/tarantool@948e5cd Closes #120
1 parent e0b420a commit 4b38d6d

File tree

10 files changed

+705
-45
lines changed

10 files changed

+705
-45
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1010

1111
### Added
1212

13+
- Support iproto feature discovery (#120).
14+
1315
### Changed
1416

1517
### Fixed

connection.go

+156
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"math"
1515
"net"
1616
"runtime"
17+
"strings"
1718
"sync"
1819
"sync/atomic"
1920
"time"
@@ -146,6 +147,8 @@ type Connection struct {
146147
lenbuf [PacketLengthBytes]byte
147148

148149
lastStreamId uint64
150+
151+
serverProtocolInfo ProtocolInfo
149152
}
150153

151154
var _ = Connector(&Connection{}) // Check compatibility with connector interface.
@@ -269,6 +272,14 @@ type Opts struct {
269272
Transport string
270273
// SslOpts is used only if the Transport == 'ssl' is set.
271274
Ssl SslOpts
275+
// Minimal protocol version that should be supported by
276+
// Go connection client and Tarantool server. By default
277+
// it is equal to 0 (no restrictions)
278+
RequiredProtocolVersion ProtocolVersion
279+
// List of protocol features that should be supported by
280+
// Go connection client and Tarantool server. By default
281+
// it is a nil array (no restrictions)
282+
RequiredProtocolFeatures []ProtocolFeature
272283
}
273284

274285
// SslOpts is a way to configure ssl transport.
@@ -296,6 +307,9 @@ type SslOpts struct {
296307
func (opts Opts) Copy() (optsCopy Opts) {
297308
optsCopy = opts
298309

310+
optsCopy.RequiredProtocolFeatures = make([]ProtocolFeature, len(opts.RequiredProtocolFeatures))
311+
copy(optsCopy.RequiredProtocolFeatures, opts.RequiredProtocolFeatures)
312+
299313
return optsCopy
300314
}
301315

@@ -508,6 +522,23 @@ func (conn *Connection) dial() (err error) {
508522
conn.Greeting.Version = bytes.NewBuffer(greeting[:64]).String()
509523
conn.Greeting.auth = bytes.NewBuffer(greeting[64:108]).String()
510524

525+
// IPROTO_ID requests can be processed without authentication.
526+
// https://www.tarantool.io/en/doc/latest/dev_guide/internals/iproto/requests/#iproto-id
527+
if err = conn.identify(w, r); err != nil {
528+
connection.Close()
529+
return err
530+
}
531+
532+
if err = checkProtocolVersion(opts.RequiredProtocolVersion, conn.ServerProtocolVersion()); err != nil {
533+
connection.Close()
534+
return fmt.Errorf("identify: %w", err)
535+
}
536+
537+
if err = checkProtocolFeatures(opts.RequiredProtocolFeatures, conn.ServerProtocolFeatures()); err != nil {
538+
connection.Close()
539+
return fmt.Errorf("identify: %w", err)
540+
}
541+
511542
// Auth
512543
if opts.User != "" {
513544
scr, err := scramble(conn.Greeting.auth, opts.Pass)
@@ -614,6 +645,18 @@ func (conn *Connection) writeAuthRequest(w *bufio.Writer, scramble []byte) (err
614645
return nil
615646
}
616647

648+
func (conn *Connection) writeIdRequest(w *bufio.Writer, version ProtocolVersion,
649+
features []ProtocolFeature) (err error) {
650+
req := NewIdRequest(version, features)
651+
652+
err = conn.writeRequest(w, req)
653+
if err != nil {
654+
return fmt.Errorf("identify: %w", err)
655+
}
656+
657+
return nil
658+
}
659+
617660
func (conn *Connection) readResponse(r io.Reader) (resp Response, err error) {
618661
respBytes, err := conn.read(r)
619662
if err != nil {
@@ -645,6 +688,15 @@ func (conn *Connection) readAuthResponse(r io.Reader) (err error) {
645688
return nil
646689
}
647690

691+
func (conn *Connection) readIdResponse(r io.Reader) (resp Response, err error) {
692+
resp, err = conn.readResponse(r)
693+
if err != nil {
694+
return resp, fmt.Errorf("identify: %w", err)
695+
}
696+
697+
return resp, nil
698+
}
699+
648700
func (conn *Connection) createConnection(reconnect bool) (err error) {
649701
var reconnects uint
650702
for conn.c == nil && conn.state == connDisconnected {
@@ -1188,3 +1240,107 @@ func (conn *Connection) NewStream() (*Stream, error) {
11881240
Conn: conn,
11891241
}, nil
11901242
}
1243+
1244+
// checkProtocolVersion checks that expected protocol version is supported.
1245+
func checkProtocolVersion(expected ProtocolVersion, actual ProtocolVersion) error {
1246+
if expected > actual {
1247+
return fmt.Errorf("protocol version %d is not supported", expected)
1248+
}
1249+
1250+
return nil
1251+
}
1252+
1253+
// checkProtocolFeatures checks that expected protocol features are supported.
1254+
func checkProtocolFeatures(expectedList []ProtocolFeature, actualList []ProtocolFeature) error {
1255+
var found bool
1256+
var missingList []ProtocolFeature
1257+
// It seems that iterating over a small list is way faster
1258+
// than building a map: https://stackoverflow.com/a/52710077/11646599
1259+
for _, expected := range expectedList {
1260+
found = false
1261+
for _, actual := range actualList {
1262+
if expected == actual {
1263+
found = true
1264+
}
1265+
}
1266+
if !found {
1267+
missingList = append(missingList, expected)
1268+
}
1269+
}
1270+
1271+
if len(missingList) == 1 {
1272+
return fmt.Errorf("protocol feature %s is not supported", missingList[0])
1273+
} else if len(missingList) > 1 {
1274+
var sarr []string
1275+
for _, missing := range missingList {
1276+
sarr = append(sarr, missing.String())
1277+
}
1278+
return fmt.Errorf("protocol features %s are not supported", strings.Join(sarr, ", "))
1279+
}
1280+
1281+
return nil
1282+
}
1283+
1284+
// identify sends info about client protocol, receives info
1285+
// about server protocol in response and store it in the connection.
1286+
func (conn *Connection) identify(w *bufio.Writer, r *bufio.Reader) error {
1287+
var ok bool
1288+
1289+
werr := conn.writeIdRequest(w, conn.ClientProtocolVersion(), conn.ClientProtocolFeatures())
1290+
if werr != nil {
1291+
return werr
1292+
}
1293+
1294+
resp, rerr := conn.readIdResponse(r)
1295+
if rerr != nil {
1296+
if resp.Code == ErrUnknownRequestType {
1297+
// IPROTO_ID requests are not supported by server.
1298+
return nil
1299+
}
1300+
1301+
return rerr
1302+
}
1303+
1304+
if len(resp.Data) == 0 {
1305+
return fmt.Errorf("identify: unexpected response: no data")
1306+
}
1307+
1308+
conn.serverProtocolInfo, ok = resp.Data[0].(ProtocolInfo)
1309+
if !ok {
1310+
return fmt.Errorf("identify: unexpected response: wrong data")
1311+
}
1312+
1313+
return nil
1314+
}
1315+
1316+
// ServerProtocolVersion returns protocol version supported by
1317+
// connected Tarantool server. Beware that values might be outdated
1318+
// if connection is in a disconnected state.
1319+
// Since 1.10.0
1320+
func (conn *Connection) ServerProtocolVersion() ProtocolVersion {
1321+
return conn.serverProtocolInfo.Version
1322+
}
1323+
1324+
// ClientProtocolVersion returns protocol version supported by
1325+
// Go connection client.
1326+
// Since 1.10.0
1327+
func (conn *Connection) ClientProtocolVersion() ProtocolVersion {
1328+
return сlientProtocolVersion
1329+
}
1330+
1331+
// ServerFeatures returns protocol features supported by connected Tarantool server.
1332+
// Beware that values might be outdated if connection is in a disconnected state.
1333+
// Since 1.10.0
1334+
func (conn *Connection) ServerProtocolFeatures() []ProtocolFeature {
1335+
res := make([]ProtocolFeature, len(conn.serverProtocolInfo.Features))
1336+
copy(res, conn.serverProtocolInfo.Features)
1337+
return res
1338+
}
1339+
1340+
// ClientFeatures returns protocol features supported by Go connection client.
1341+
// Since 1.10.0
1342+
func (conn *Connection) ClientProtocolFeatures() []ProtocolFeature {
1343+
res := make([]ProtocolFeature, len(сlientProtocolFeatures))
1344+
copy(res, сlientProtocolFeatures)
1345+
return res
1346+
}

0 commit comments

Comments
 (0)