Commit 75793056 authored by galal-hussein's avatar galal-hussein

update dqlite-build and kine

parent d4c17223
...@@ -22,7 +22,7 @@ ENV SELINUX $SELINUX ...@@ -22,7 +22,7 @@ ENV SELINUX $SELINUX
ARG DQLITE=true ARG DQLITE=true
ENV DQLITE $DQLITE ENV DQLITE $DQLITE
COPY --from=rancher/dqlite-build:v1.3.1-r1 /dist/artifacts /usr/src/ COPY --from=rancher/dqlite-build:v1.4.1-r1 /dist/artifacts /usr/src/
RUN if [ "$DQLITE" = true ]; then \ RUN if [ "$DQLITE" = true ]; then \
tar xzf /usr/src/dqlite.tgz -C / && \ tar xzf /usr/src/dqlite.tgz -C / && \
apk add --allow-untrusted /usr/local/packages/*.apk \ apk add --allow-untrusted /usr/local/packages/*.apk \
......
...@@ -64,7 +64,7 @@ require ( ...@@ -64,7 +64,7 @@ require (
github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e // indirect github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e // indirect
github.com/bronze1man/goStrongswanVici v0.0.0-20190828090544-27d02f80ba40 // indirect github.com/bronze1man/goStrongswanVici v0.0.0-20190828090544-27d02f80ba40 // indirect
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect
github.com/canonical/go-dqlite v1.3.0 github.com/canonical/go-dqlite v1.5.1
github.com/containerd/cgroups v0.0.0-00010101000000-000000000000 // indirect github.com/containerd/cgroups v0.0.0-00010101000000-000000000000 // indirect
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69 github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 // indirect github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 // indirect
...@@ -99,7 +99,7 @@ require ( ...@@ -99,7 +99,7 @@ require (
github.com/rakelkar/gonetsh v0.0.0-20190719023240-501daadcadf8 // indirect github.com/rakelkar/gonetsh v0.0.0-20190719023240-501daadcadf8 // indirect
github.com/rancher/dynamiclistener v0.2.0 github.com/rancher/dynamiclistener v0.2.0
github.com/rancher/helm-controller v0.4.1 github.com/rancher/helm-controller v0.4.1
github.com/rancher/kine v0.3.6-0.20200422224205-0a0f5b924129 github.com/rancher/kine v0.4.0
github.com/rancher/remotedialer v0.2.0 github.com/rancher/remotedialer v0.2.0
github.com/rancher/wrangler v0.4.0 github.com/rancher/wrangler v0.4.0
github.com/rancher/wrangler-api v0.4.0 github.com/rancher/wrangler-api v0.4.0
......
...@@ -116,6 +116,8 @@ github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ ...@@ -116,6 +116,8 @@ github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ
github.com/canonical/go-dqlite v1.2.0/go.mod h1:wp00vfMvPYgNCyxcPdHB5XExmDoCGoPUGymloAQT17Y= github.com/canonical/go-dqlite v1.2.0/go.mod h1:wp00vfMvPYgNCyxcPdHB5XExmDoCGoPUGymloAQT17Y=
github.com/canonical/go-dqlite v1.3.0 h1:c+7eGZfh0K7yCmGrBkNRGZdY8R8+2jSSkz6Zr3YCjJE= github.com/canonical/go-dqlite v1.3.0 h1:c+7eGZfh0K7yCmGrBkNRGZdY8R8+2jSSkz6Zr3YCjJE=
github.com/canonical/go-dqlite v1.3.0/go.mod h1:wp00vfMvPYgNCyxcPdHB5XExmDoCGoPUGymloAQT17Y= github.com/canonical/go-dqlite v1.3.0/go.mod h1:wp00vfMvPYgNCyxcPdHB5XExmDoCGoPUGymloAQT17Y=
github.com/canonical/go-dqlite v1.5.1 h1:1YjtIrFsC1A3XlgsX38ARAiKhvkZS63PqsEd8z3T4yU=
github.com/canonical/go-dqlite v1.5.1/go.mod h1:wp00vfMvPYgNCyxcPdHB5XExmDoCGoPUGymloAQT17Y=
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
...@@ -724,6 +726,8 @@ github.com/rancher/kine v0.3.5 h1:Tm4eOtejpnzs1WFBrXj76lCLvX9czLlTkgqUk9luCQk= ...@@ -724,6 +726,8 @@ github.com/rancher/kine v0.3.5 h1:Tm4eOtejpnzs1WFBrXj76lCLvX9czLlTkgqUk9luCQk=
github.com/rancher/kine v0.3.5/go.mod h1:xEMl0tLCva9/9me7mXJ3m9Vo6yqHgC4OU3NiK4CPrGQ= github.com/rancher/kine v0.3.5/go.mod h1:xEMl0tLCva9/9me7mXJ3m9Vo6yqHgC4OU3NiK4CPrGQ=
github.com/rancher/kine v0.3.6-0.20200422224205-0a0f5b924129 h1:4HYvCG8+pfkBlpYLv/5ZOdxagg3jTszMOqMjamMQ0hA= github.com/rancher/kine v0.3.6-0.20200422224205-0a0f5b924129 h1:4HYvCG8+pfkBlpYLv/5ZOdxagg3jTszMOqMjamMQ0hA=
github.com/rancher/kine v0.3.6-0.20200422224205-0a0f5b924129/go.mod h1:xEMl0tLCva9/9me7mXJ3m9Vo6yqHgC4OU3NiK4CPrGQ= github.com/rancher/kine v0.3.6-0.20200422224205-0a0f5b924129/go.mod h1:xEMl0tLCva9/9me7mXJ3m9Vo6yqHgC4OU3NiK4CPrGQ=
github.com/rancher/kine v0.4.0 h1:1IhWy3TzjExG8xnj46eyUEWdzqNAD1WrgL4eEBKm6Uc=
github.com/rancher/kine v0.4.0/go.mod h1:IImtCJ68AIkE+VY/kUI0NkyJL5q5WzO8QvMsSXqbrpA=
github.com/rancher/kubernetes v1.17.5-k3s1 h1:rE+lSQ7SszfLZ4KB94FPQtvqQPYoU6Jwul8HHBPqQcY= github.com/rancher/kubernetes v1.17.5-k3s1 h1:rE+lSQ7SszfLZ4KB94FPQtvqQPYoU6Jwul8HHBPqQcY=
github.com/rancher/kubernetes v1.17.5-k3s1/go.mod h1:4A7Bm7SHrj2SCb9vxMBil1wu2uJy3k1zSjoD43SS8Zg= github.com/rancher/kubernetes v1.17.5-k3s1/go.mod h1:4A7Bm7SHrj2SCb9vxMBil1wu2uJy3k1zSjoD43SS8Zg=
github.com/rancher/kubernetes/staging/src/k8s.io/api v1.17.5-k3s1 h1:/6jG49ioPbxPlk+wY3XQTj3JjVoGuE/5Ozx3tZGk32g= github.com/rancher/kubernetes/staging/src/k8s.io/api v1.17.5-k3s1 h1:/6jG49ioPbxPlk+wY3XQTj3JjVoGuE/5Ozx3tZGk32g=
......
.sqlite .sqlite
cmd/dqlite/dqlite
cmd/dqlite-demo/dqlite-demo
demo demo
profile.coverprofile profile.coverprofile
overalls.coverprofile overalls.coverprofile
...@@ -10,9 +10,32 @@ Usage ...@@ -10,9 +10,32 @@ Usage
The best way to understand how to use the ```go-dqlite``` package is probably by The best way to understand how to use the ```go-dqlite``` package is probably by
looking at the source code of the [demo looking at the source code of the [demo
program](https://github.com/canonical/go-dqlite/tree/master/cmd/dqlite-demo) and program](https://github.com/canonical/go-dqlite/tree/master/cmd/dqlite-demo/main.go) and
use it as example. use it as example.
In general your application will use code such as:
```go
dir := "/path/to/data/directory"
address := "1.2.3.4:666" // Unique node address
cluster := []string{...} // Optional list of existing nodes, when starting a new node
app, err := app.New(dir, app.WithAddress(address), app.WithCluster(cluster))
if err != nil {
// ...
}
db, err := app.Open(context.Background(), "my-database")
if err != nil {
// ...
}
// db is a *sql.DB object
if _, err := db.Exec("CREATE TABLE my_table (n INT)"); err != nil
// ...
}
```
Build Build
----- -----
...@@ -50,42 +73,61 @@ go install -tags libsqlite3 ./cmd/dqlite-demo ...@@ -50,42 +73,61 @@ go install -tags libsqlite3 ./cmd/dqlite-demo
from the top-level directory of this repository. from the top-level directory of this repository.
Once the ```dqlite-demo``` binary is installed, start three nodes of the demo This builds a demo dqlite application, which exposes a simple key/value store
application, respectively with IDs ```1```, ```2,``` and ```3```: over an HTTP API.
Once the `dqlite-demo` binary is installed (normally under `~/go/bin`),
start three nodes of the demo application:
```bash ```bash
dqlite-demo start 1 & dqlite-demo --api 127.0.0.1:8001 --db 127.0.0.1:9001 &
dqlite-demo start 2 & dqlite-demo --api 127.0.0.1:8002 --db 127.0.0.1:9002 --join 127.0.0.1:9001 &
dqlite-demo start 3 & dqlite-demo --api 127.0.0.1:8003 --db 127.0.0.1:9003 --join 127.0.0.1:9001 &
``` ```
The node with ID ```1``` automatically becomes the leader of a single node The `--api` flag tells the demo program where to expose its HTTP API.
cluster, while the nodes with IDs ```2``` and ```3``` are waiting to be notified
what cluster they belong to. Let's make nodes ```2``` and ```3``` join the The `--db` flag tells the demo program to use the given address for internal
cluster: database replication.
The `--join` flag is optional and should be used only for additional nodes after
the first one. It informs them about the existing cluster, so they can
automatically join it.
Now we can start using the cluster. Let's insert a key pair:
```bash ```bash
dqlite-demo add 2 curl -X PUT -d my-key http://127.0.0.1:8001/my-value
dqlite-demo add 3
``` ```
Now we can start using the cluster. The demo application is just a simple and then retrive it from the database:
key/value store that stores data in a SQLite table. Let's insert a key pair:
```bash ```bash
dqlite-demo update my-key my-value curl http://127.0.0.1:8001/my-value
``` ```
and then retrive it from the database: Currently the first node is the leader. If we stop it and then try to query the
key again curl will fail, but we can simply change the endpoint to another node
and things will work since an automatic failover has taken place:
```bash ```bash
dqlite-demo query my-key kill -TERM %1; curl http://127.0.0.1:8002/my-value
``` ```
Currently node ```1``` is the leader. If we stop it and then try to query the Shell
key again we'll notice that the ```query``` command hangs for a bit waiting for ------
the failover to occur and for another node to step up as leader:
A basic SQLite-like dqlite shell can be built with:
``` ```
kill -TERM %1; sleep 0.1; dqlite-demo query my-key; dqlite-demo cluster go install -tags libsqlite3 ./cmd/dqlite
``` ```
You can test it with the `dqlite-demo` with:
```
dqlite -s 127.0.0.1:9001
```
It supports normal SQL queries plus the special `.cluster` and `.leader`
commands to inspect the cluster members and the current leader.
...@@ -2,10 +2,6 @@ package client ...@@ -2,10 +2,6 @@ package client
import ( import (
"context" "context"
"encoding/binary"
"io"
"net"
"strings"
"github.com/canonical/go-dqlite/internal/protocol" "github.com/canonical/go-dqlite/internal/protocol"
"github.com/pkg/errors" "github.com/pkg/errors"
...@@ -57,22 +53,13 @@ func New(ctx context.Context, address string, options ...Option) (*Client, error ...@@ -57,22 +53,13 @@ func New(ctx context.Context, address string, options ...Option) (*Client, error
return nil, errors.Wrap(err, "failed to establish network connection") return nil, errors.Wrap(err, "failed to establish network connection")
} }
// Latest protocol version. protocol, err := protocol.Handshake(ctx, conn, protocol.VersionOne)
proto := make([]byte, 8)
binary.LittleEndian.PutUint64(proto, protocol.VersionOne)
// Perform the protocol handshake.
n, err := conn.Write(proto)
if err != nil { if err != nil {
conn.Close() conn.Close()
return nil, errors.Wrap(err, "failed to send handshake") return nil, err
}
if n != 8 {
conn.Close()
return nil, errors.Wrap(io.ErrShortWrite, "failed to send handshake")
} }
client := &Client{protocol: protocol.NewProtocol(protocol.VersionOne, conn)} client := &Client{protocol: protocol}
return client, nil return client, nil
} }
...@@ -274,10 +261,3 @@ func defaultOptions() *options { ...@@ -274,10 +261,3 @@ func defaultOptions() *options {
LogFunc: DefaultLogFunc, LogFunc: DefaultLogFunc,
} }
} }
func DefaultDialFunc(ctx context.Context, address string) (net.Conn, error) {
if strings.HasPrefix(address, "@") {
return protocol.UnixDial(ctx, address)
}
return protocol.TCPDial(ctx, address)
}
package client
import (
"context"
"crypto/tls"
"net"
"strings"
"github.com/canonical/go-dqlite/internal/protocol"
)
// DefaultDialFunc is the default dial function, which can handle plain TCP and
// Unix socket endpoints. You can customize it with WithDialFunc()
func DefaultDialFunc(ctx context.Context, address string) (net.Conn, error) {
if strings.HasPrefix(address, "@") {
return protocol.UnixDial(ctx, address)
}
return protocol.TCPDial(ctx, address)
}
// DialFuncWithTLS returns a dial function that uses TLS encryption.
//
// The given dial function will be used to establish the network connection,
// and the given TLS config will be used for encryption.
func DialFuncWithTLS(dial DialFunc, config *tls.Config) DialFunc {
return func(ctx context.Context, addr string) (net.Conn, error) {
conn, err := dial(ctx, addr)
if err != nil {
return nil, err
}
return tls.Client(conn, config), nil
}
}
...@@ -2,10 +2,7 @@ package client ...@@ -2,10 +2,7 @@ package client
import ( import (
"context" "context"
"time"
"github.com/Rican7/retry/backoff"
"github.com/Rican7/retry/strategy"
"github.com/canonical/go-dqlite/internal/protocol" "github.com/canonical/go-dqlite/internal/protocol"
) )
...@@ -18,10 +15,7 @@ func FindLeader(ctx context.Context, store NodeStore, options ...Option) (*Clien ...@@ -18,10 +15,7 @@ func FindLeader(ctx context.Context, store NodeStore, options ...Option) (*Clien
} }
config := protocol.Config{ config := protocol.Config{
Dial: o.DialFunc, Dial: o.DialFunc,
AttemptTimeout: time.Second,
RetryStrategies: []strategy.Strategy{
strategy.Backoff(backoff.BinaryExponential(time.Millisecond))},
} }
connector := protocol.NewConnector(0, store, config, o.LogFunc) connector := protocol.NewConnector(0, store, config, o.LogFunc)
protocol, err := connector.Connect(ctx) protocol, err := connector.Connect(ctx)
......
package client package client
import ( import (
"fmt"
"log"
"os"
"github.com/canonical/go-dqlite/internal/logging" "github.com/canonical/go-dqlite/internal/logging"
) )
...@@ -22,11 +18,5 @@ const ( ...@@ -22,11 +18,5 @@ const (
LogError = logging.Error LogError = logging.Error
) )
var ( // DefaultLogFunc doesn't emit any message.
logger = log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds) func DefaultLogFunc(l LogLevel, format string, a ...interface{}) {}
)
// DefaultLogFunc emits messages using the stdlib's logger.
func DefaultLogFunc(l LogLevel, format string, a ...interface{}) {
logger.Output(2, fmt.Sprintf("[%s]: %s", l.String(), fmt.Sprintf(format, a...)))
}
...@@ -4,7 +4,11 @@ import ( ...@@ -4,7 +4,11 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"io/ioutil"
"os"
"sync"
"github.com/ghodss/yaml"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/canonical/go-dqlite/internal/protocol" "github.com/canonical/go-dqlite/internal/protocol"
...@@ -163,3 +167,65 @@ func (d *DatabaseNodeStore) Set(ctx context.Context, servers []NodeInfo) error { ...@@ -163,3 +167,65 @@ func (d *DatabaseNodeStore) Set(ctx context.Context, servers []NodeInfo) error {
return nil return nil
} }
// Persists a list addresses of dqlite nodes in a YAML file.
type YamlNodeStore struct {
path string
servers []NodeInfo
mu sync.RWMutex
}
// NewYamlNodeStore creates a new YamlNodeStore backed by the given YAML file.
func NewYamlNodeStore(path string) (*YamlNodeStore, error) {
servers := []NodeInfo{}
_, err := os.Stat(path)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
} else {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
if err := yaml.Unmarshal(data, &servers); err != nil {
return nil, err
}
}
store := &YamlNodeStore{
path: path,
servers: servers,
}
return store, nil
}
// Get the current servers.
func (s *YamlNodeStore) Get(ctx context.Context) ([]NodeInfo, error) {
s.mu.RLock()
defer s.mu.RUnlock()
return s.servers, nil
}
// Set the servers addresses.
func (s *YamlNodeStore) Set(ctx context.Context, servers []NodeInfo) error {
s.mu.Lock()
defer s.mu.Unlock()
data, err := yaml.Marshal(servers)
if err != nil {
return err
}
if err := ioutil.WriteFile(s.path, data, 0600); err != nil {
return err
}
s.servers = servers
return nil
}
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"os" "os"
"github.com/canonical/go-dqlite/internal/bindings" "github.com/canonical/go-dqlite/internal/bindings"
"github.com/canonical/go-dqlite/internal/protocol"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
...@@ -26,7 +27,7 @@ import ( ...@@ -26,7 +27,7 @@ import (
// from setting Single-thread mode at all. // from setting Single-thread mode at all.
func ConfigMultiThread() error { func ConfigMultiThread() error {
if err := bindings.ConfigMultiThread(); err != nil { if err := bindings.ConfigMultiThread(); err != nil {
if err, ok := err.(bindings.Error); ok && err.Code == 21 /* SQLITE_MISUSE */ { if err, ok := err.(protocol.Error); ok && err.Code == 21 /* SQLITE_MISUSE */ {
return fmt.Errorf("SQLite is already initialized") return fmt.Errorf("SQLite is already initialized")
} }
return errors.Wrap(err, "unknown error") return errors.Wrap(err, "unknown error")
......
package bindings
/*
#include <sqlite3.h>
*/
import "C"
// Error holds information about a SQLite error.
type Error struct {
Code int
Message string
}
func (e Error) Error() string {
if e.Message != "" {
return e.Message
}
return C.GoString(C.sqlite3_errstr(C.int(e.Code)))
}
...@@ -90,14 +90,14 @@ func init() { ...@@ -90,14 +90,14 @@ func init() {
func ConfigSingleThread() error { func ConfigSingleThread() error {
if rc := C.sqlite3ConfigSingleThread(); rc != 0 { if rc := C.sqlite3ConfigSingleThread(); rc != 0 {
return Error{Code: int(rc)} return protocol.Error{Message: C.GoString(C.sqlite3_errstr(rc)), Code: int(rc)}
} }
return nil return nil
} }
func ConfigMultiThread() error { func ConfigMultiThread() error {
if rc := C.sqlite3ConfigMultiThread(); rc != 0 { if rc := C.sqlite3ConfigMultiThread(); rc != 0 {
return Error{Code: int(rc)} return protocol.Error{Message: C.GoString(C.sqlite3_errstr(rc)), Code: int(rc)}
} }
return nil return nil
} }
...@@ -197,6 +197,14 @@ func (s *Node) Recover(cluster []protocol.NodeInfo) error { ...@@ -197,6 +197,14 @@ func (s *Node) Recover(cluster []protocol.NodeInfo) error {
return nil return nil
} }
// GenerateID generates a unique ID for a server.
func GenerateID(address string) uint64 {
caddress := C.CString(address)
defer C.free(unsafe.Pointer(caddress))
id := C.dqlite_generate_node_id(caddress)
return uint64(id)
}
// Extract the underlying socket from a connection. // Extract the underlying socket from a connection.
func connToSocket(conn net.Conn) (C.int, error) { func connToSocket(conn net.Conn) (C.int, error) {
file, err := conn.(fileConn).File() file, err := conn.(fileConn).File()
......
...@@ -2,13 +2,14 @@ package protocol ...@@ -2,13 +2,14 @@ package protocol
import ( import (
"time" "time"
"github.com/Rican7/retry/strategy"
) )
// Config holds various configuration parameters for a dqlite client. // Config holds various configuration parameters for a dqlite client.
type Config struct { type Config struct {
Dial DialFunc // Network dialer. Dial DialFunc // Network dialer.
AttemptTimeout time.Duration // Timeout for each individual Dial attempt. DialTimeout time.Duration // Timeout for establishing a network connection .
RetryStrategies []strategy.Strategy // Strategies used for retrying to connect to a leader. AttemptTimeout time.Duration // Timeout for each individual attempt to probe a server's leadership.
BackoffFactor time.Duration // Exponential backoff factor for retries.
BackoffCap time.Duration // Maximum connection retry backoff value,
RetryLimit uint // Maximum number of retries, or 0 for unlimited.
} }
...@@ -6,8 +6,11 @@ import ( ...@@ -6,8 +6,11 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"time"
"github.com/Rican7/retry" "github.com/Rican7/retry"
"github.com/Rican7/retry/backoff"
"github.com/Rican7/retry/strategy"
"github.com/canonical/go-dqlite/internal/logging" "github.com/canonical/go-dqlite/internal/logging"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
...@@ -27,6 +30,22 @@ type Connector struct { ...@@ -27,6 +30,22 @@ type Connector struct {
// NewConnector returns a new connector that can be used by a dqlite driver to // NewConnector returns a new connector that can be used by a dqlite driver to
// create new clients connected to a leader dqlite server. // create new clients connected to a leader dqlite server.
func NewConnector(id uint64, store NodeStore, config Config, log logging.Func) *Connector { func NewConnector(id uint64, store NodeStore, config Config, log logging.Func) *Connector {
if config.DialTimeout == 0 {
config.DialTimeout = 10 * time.Second
}
if config.AttemptTimeout == 0 {
config.AttemptTimeout = 60 * time.Second
}
if config.BackoffFactor == 0 {
config.BackoffFactor = 100 * time.Millisecond
}
if config.BackoffCap == 0 {
config.BackoffCap = time.Second
}
connector := &Connector{ connector := &Connector{
id: id, id: id,
store: store, store: store,
...@@ -43,6 +62,8 @@ func NewConnector(id uint64, store NodeStore, config Config, log logging.Func) * ...@@ -43,6 +62,8 @@ func NewConnector(id uint64, store NodeStore, config Config, log logging.Func) *
func (c *Connector) Connect(ctx context.Context) (*Protocol, error) { func (c *Connector) Connect(ctx context.Context) (*Protocol, error) {
var protocol *Protocol var protocol *Protocol
strategies := makeRetryStrategies(c.config.BackoffFactor, c.config.BackoffCap, c.config.RetryLimit)
// The retry strategy should be configured to retry indefinitely, until // The retry strategy should be configured to retry indefinitely, until
// the given context is done. // the given context is done.
err := retry.Retry(func(attempt uint) error { err := retry.Retry(func(attempt uint) error {
...@@ -61,17 +82,16 @@ func (c *Connector) Connect(ctx context.Context) (*Protocol, error) { ...@@ -61,17 +82,16 @@ func (c *Connector) Connect(ctx context.Context) (*Protocol, error) {
var err error var err error
protocol, err = c.connectAttemptAll(ctx, log) protocol, err = c.connectAttemptAll(ctx, log)
if err != nil { if err != nil {
log(logging.Debug, "connection failed err=%v", err)
return err return err
} }
return nil return nil
}, c.config.RetryStrategies...) }, strategies...)
if err != nil { if err != nil {
// The retry strategy should never give up until success or // We exhausted the number of retries allowed by the configured
// context expiration. // strategy.
panic("connect retry aborted unexpectedly") return nil, ErrNoAvailableLeader
} }
if ctx.Err() != nil { if ctx.Err() != nil {
...@@ -92,7 +112,7 @@ func (c *Connector) connectAttemptAll(ctx context.Context, log logging.Func) (*P ...@@ -92,7 +112,7 @@ func (c *Connector) connectAttemptAll(ctx context.Context, log logging.Func) (*P
// Make an attempt for each address until we find the leader. // Make an attempt for each address until we find the leader.
for _, server := range servers { for _, server := range servers {
log := func(l logging.Level, format string, a ...interface{}) { log := func(l logging.Level, format string, a ...interface{}) {
format += fmt.Sprintf(" address=%s id=%d", server.Address, server.ID) format += fmt.Sprintf(" address=%s", server.Address)
log(l, format, a...) log(l, format, a...)
} }
...@@ -107,68 +127,71 @@ func (c *Connector) connectAttemptAll(ctx context.Context, log logging.Func) (*P ...@@ -107,68 +127,71 @@ func (c *Connector) connectAttemptAll(ctx context.Context, log logging.Func) (*P
} }
if err != nil { if err != nil {
// This server is unavailable, try with the next target. // This server is unavailable, try with the next target.
log(logging.Debug, "server connection failed err=%v", err) log(logging.Warn, "server unavailable err=%v", err)
continue continue
} }
if protocol != nil { if protocol != nil {
// We found the leader // We found the leader
log(logging.Info, "connected") log(logging.Debug, "connected")
return protocol, nil return protocol, nil
} }
if leader == "" { if leader == "" {
// This server does not know who the current leader is, // This server does not know who the current leader is,
// try with the next target. // try with the next target.
log(logging.Warn, "no known leader")
continue continue
} }
// If we get here, it means this server reported that another // If we get here, it means this server reported that another
// server is the leader, let's close the connection to this // server is the leader, let's close the connection to this
// server and try with the suggested one. // server and try with the suggested one.
//logger = logger.With(zap.String("leader", leader)) log(logging.Debug, "connect to reported leader %s", leader)
ctx, cancel = context.WithTimeout(ctx, c.config.AttemptTimeout)
defer cancel()
protocol, leader, err = c.connectAttemptOne(ctx, leader, version) protocol, leader, err = c.connectAttemptOne(ctx, leader, version)
if err != nil { if err != nil {
// The leader reported by the previous server is // The leader reported by the previous server is
// unavailable, try with the next target. // unavailable, try with the next target.
//logger.Info("leader server connection failed", zap.String("err", err.Error())) log(logging.Warn, "reported leader unavailable err=%v", err)
continue continue
} }
if protocol == nil { if protocol == nil {
// The leader reported by the target server does not consider itself // The leader reported by the target server does not consider itself
// the leader, try with the next target. // the leader, try with the next target.
//logger.Info("reported leader server is not the leader") log(logging.Warn, "reported leader server is not the leader")
continue continue
} }
log(logging.Info, "connected") log(logging.Debug, "connected")
return protocol, nil return protocol, nil
} }
return nil, ErrNoAvailableLeader return nil, ErrNoAvailableLeader
} }
// Connect establishes a connection with a dqlite node. // Perform the initial handshake using the given protocol version.
func Connect(ctx context.Context, dial DialFunc, address string, version uint64) (*Protocol, error) { func Handshake(ctx context.Context, conn net.Conn, version uint64) (*Protocol, error) {
// Establish the connection.
conn, err := dial(ctx, address)
if err != nil {
return nil, errors.Wrap(err, "failed to establish network connection")
}
// Latest protocol version. // Latest protocol version.
protocol := make([]byte, 8) protocol := make([]byte, 8)
binary.LittleEndian.PutUint64(protocol, version) binary.LittleEndian.PutUint64(protocol, version)
// Honor the ctx deadline, if present.
if deadline, ok := ctx.Deadline(); ok {
conn.SetDeadline(deadline)
defer conn.SetDeadline(time.Time{})
}
// Perform the protocol handshake. // Perform the protocol handshake.
n, err := conn.Write(protocol) n, err := conn.Write(protocol)
if err != nil { if err != nil {
conn.Close()
return nil, errors.Wrap(err, "failed to send handshake") return nil, errors.Wrap(err, "failed to send handshake")
} }
if n != 8 { if n != 8 {
conn.Close()
return nil, errors.Wrap(io.ErrShortWrite, "failed to send handshake") return nil, errors.Wrap(io.ErrShortWrite, "failed to send handshake")
} }
return NewProtocol(version, conn), nil return newProtocol(version, conn), nil
} }
// Connect to the given dqlite server and check if it's the leader. // Connect to the given dqlite server and check if it's the leader.
...@@ -181,8 +204,18 @@ func Connect(ctx context.Context, dial DialFunc, address string, version uint64) ...@@ -181,8 +204,18 @@ func Connect(ctx context.Context, dial DialFunc, address string, version uint64)
// - Target is the leader: -> server, "", nil // - Target is the leader: -> server, "", nil
// //
func (c *Connector) connectAttemptOne(ctx context.Context, address string, version uint64) (*Protocol, string, error) { func (c *Connector) connectAttemptOne(ctx context.Context, address string, version uint64) (*Protocol, string, error) {
protocol, err := Connect(ctx, c.config.Dial, address, version) dialCtx, cancel := context.WithTimeout(ctx, c.config.DialTimeout)
defer cancel()
// Establish the connection.
conn, err := c.config.Dial(dialCtx, address)
if err != nil {
return nil, "", errors.Wrap(err, "failed to establish network connection")
}
protocol, err := Handshake(ctx, conn, version)
if err != nil { if err != nil {
conn.Close()
return nil, "", err return nil, "", err
} }
...@@ -219,8 +252,8 @@ func (c *Connector) connectAttemptOne(ctx context.Context, address string, versi ...@@ -219,8 +252,8 @@ func (c *Connector) connectAttemptOne(ctx context.Context, address string, versi
return nil, "", nil return nil, "", nil
case address: case address:
// This server is the leader, register ourselves and return. // This server is the leader, register ourselves and return.
request.Reset() request.reset()
response.Reset() response.reset()
EncodeClient(&request, c.id) EncodeClient(&request, c.id)
...@@ -247,4 +280,33 @@ func (c *Connector) connectAttemptOne(ctx context.Context, address string, versi ...@@ -247,4 +280,33 @@ func (c *Connector) connectAttemptOne(ctx context.Context, address string, versi
} }
} }
// Return a retry strategy with exponential backoff, capped at the given amount
// of time and possibly with a maximum number of retries.
func makeRetryStrategies(factor, cap time.Duration, limit uint) []strategy.Strategy {
backoff := backoff.BinaryExponential(factor)
strategies := []strategy.Strategy{}
if limit > 0 {
strategies = append(strategies, strategy.Limit(limit))
}
strategies = append(strategies,
func(attempt uint) bool {
if attempt > 0 {
duration := backoff(attempt)
// Duration might be negative in case of integer overflow.
if duration > cap || duration <= 0 {
duration = cap
}
time.Sleep(duration)
}
return true
},
)
return strategies
}
var errBadProtocol = fmt.Errorf("bad protocol") var errBadProtocol = fmt.Errorf("bad protocol")
...@@ -2,6 +2,7 @@ package protocol ...@@ -2,6 +2,7 @@ package protocol
import ( import (
"context" "context"
"crypto/tls"
"net" "net"
) )
...@@ -18,3 +19,15 @@ func UnixDial(ctx context.Context, address string) (net.Conn, error) { ...@@ -18,3 +19,15 @@ func UnixDial(ctx context.Context, address string) (net.Conn, error) {
dialer := net.Dialer{} dialer := net.Dialer{}
return dialer.DialContext(ctx, "unix", address) return dialer.DialContext(ctx, "unix", address)
} }
// TLSCipherSuites are the cipher suites by the go-dqlite TLS helpers.
var TLSCipherSuites = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
}
...@@ -27,3 +27,13 @@ func (e ErrRequest) Error() string { ...@@ -27,3 +27,13 @@ func (e ErrRequest) Error() string {
// ErrRowsPart is returned when the first batch of a multi-response result // ErrRowsPart is returned when the first batch of a multi-response result
// batch is done. // batch is done.
var ErrRowsPart = fmt.Errorf("not all rows were returned in this response") var ErrRowsPart = fmt.Errorf("not all rows were returned in this response")
// Error holds information about a SQLite error.
type Error struct {
Code int
Message string
}
func (e Error) Error() string {
return e.Message
}
...@@ -26,24 +26,23 @@ type Message struct { ...@@ -26,24 +26,23 @@ type Message struct {
flags uint8 flags uint8
extra uint16 extra uint16
header []byte // Statically allocated header buffer header []byte // Statically allocated header buffer
body1 buffer // Statically allocated body data, using bytes body buffer // Message body data.
body2 buffer // Dynamically allocated body data
} }
// Init initializes the message using the given size of the statically // Init initializes the message using the given initial size for the data
// allocated buffer (i.e. a buffer which is re-used across requests or // buffer, which is re-used across requests or responses encoded or decoded
// responses encoded or decoded using this message object). // using this message object.
func (m *Message) Init(staticSize int) { func (m *Message) Init(initialBufferSize int) {
if (staticSize % messageWordSize) != 0 { if (initialBufferSize % messageWordSize) != 0 {
panic("static size is not aligned to word boundary") panic("initial buffer size is not aligned to word boundary")
} }
m.header = make([]byte, messageHeaderSize) m.header = make([]byte, messageHeaderSize)
m.body1.Bytes = make([]byte, staticSize) m.body.Bytes = make([]byte, initialBufferSize)
m.Reset() m.reset()
} }
// Reset the state of the message so it can be used to encode or decode again. // Reset the state of the message so it can be used to encode or decode again.
func (m *Message) Reset() { func (m *Message) reset() {
m.words = 0 m.words = 0
m.mtype = 0 m.mtype = 0
m.flags = 0 m.flags = 0
...@@ -51,9 +50,7 @@ func (m *Message) Reset() { ...@@ -51,9 +50,7 @@ func (m *Message) Reset() {
for i := 0; i < messageHeaderSize; i++ { for i := 0; i < messageHeaderSize; i++ {
m.header[i] = 0 m.header[i] = 0
} }
m.body1.Offset = 0 m.body.Offset = 0
m.body2.Bytes = nil
m.body2.Offset = 0
} }
// Append a byte slice to the message. // Append a byte slice to the message.
...@@ -232,11 +229,11 @@ func (m *Message) putNamedValues(values NamedValues) { ...@@ -232,11 +229,11 @@ func (m *Message) putNamedValues(values NamedValues) {
// Finalize the message by setting the message type and the number // Finalize the message by setting the message type and the number
// of words in the body (calculated from the body size). // of words in the body (calculated from the body size).
func (m *Message) putHeader(mtype uint8) { func (m *Message) putHeader(mtype uint8) {
if m.body1.Offset <= 0 { if m.body.Offset <= 0 {
panic("static offset is not positive") panic("static offset is not positive")
} }
if (m.body1.Offset % messageWordSize) != 0 { if (m.body.Offset % messageWordSize) != 0 {
panic("static body is not aligned") panic("static body is not aligned")
} }
...@@ -244,22 +241,7 @@ func (m *Message) putHeader(mtype uint8) { ...@@ -244,22 +241,7 @@ func (m *Message) putHeader(mtype uint8) {
m.flags = 0 m.flags = 0
m.extra = 0 m.extra = 0
m.words = uint32(m.body1.Offset) / messageWordSize m.words = uint32(m.body.Offset) / messageWordSize
if m.body2.Bytes == nil {
m.finalize()
return
}
if m.body2.Offset <= 0 {
panic("dynamic offset is not positive")
}
if (m.body2.Offset % messageWordSize) != 0 {
panic("dynamic body is not aligned")
}
m.words += uint32(m.body2.Offset) / messageWordSize
m.finalize() m.finalize()
} }
...@@ -276,27 +258,14 @@ func (m *Message) finalize() { ...@@ -276,27 +258,14 @@ func (m *Message) finalize() {
} }
func (m *Message) bufferForPut(size int) *buffer { func (m *Message) bufferForPut(size int) *buffer {
if m.body2.Bytes != nil { for (m.body.Offset + size) > len(m.body.Bytes) {
if (m.body2.Offset + size) > len(m.body2.Bytes) { // Grow message buffer.
// Grow body2. bytes := make([]byte, len(m.body.Bytes)*2)
// copy(bytes, m.body.Bytes)
// TODO: find a good grow strategy. m.body.Bytes = bytes
bytes := make([]byte, m.body2.Offset+size)
copy(bytes, m.body2.Bytes)
m.body2.Bytes = bytes
}
return &m.body2
} }
if (m.body1.Offset + size) > len(m.body1.Bytes) { return &m.body
m.body2.Bytes = make([]byte, size)
m.body2.Offset = 0
return &m.body2
}
return &m.body1
} }
// Return the message type and its flags. // Return the message type and its flags.
...@@ -310,31 +279,6 @@ func (m *Message) getString() string { ...@@ -310,31 +279,6 @@ func (m *Message) getString() string {
index := bytes.IndexByte(b.Bytes[b.Offset:], 0) index := bytes.IndexByte(b.Bytes[b.Offset:], 0)
if index == -1 { if index == -1 {
// Check if the string overflows in the dynamic buffer.
if b == &m.body1 && m.body2.Bytes != nil {
// Assert that this is the first read of the dynamic buffer.
if m.body2.Offset != 0 {
panic("static buffer read after dynamic buffer one")
}
index = bytes.IndexByte(m.body2.Bytes[0:], 0)
if index != -1 {
// We found the trailing part of the string.
data := b.Bytes[b.Offset:]
data = append(data, m.body2.Bytes[0:index]...)
index++
if trailing := index % messageWordSize; trailing != 0 {
// Account for padding, moving index to the next word boundary.
index += messageWordSize - trailing
}
m.body1.Offset = len(m.body1.Bytes)
m.body2.Advance(index)
return string(data)
}
}
panic("no string found") panic("no string found")
} }
s := string(b.Bytes[b.Offset : b.Offset+index]) s := string(b.Bytes[b.Offset : b.Offset+index])
...@@ -465,31 +409,23 @@ func (m *Message) getFiles() Files { ...@@ -465,31 +409,23 @@ func (m *Message) getFiles() Files {
func (m *Message) hasBeenConsumed() bool { func (m *Message) hasBeenConsumed() bool {
size := int(m.words * messageWordSize) size := int(m.words * messageWordSize)
return (m.body1.Offset == size || m.body1.Offset == len(m.body1.Bytes)) && return m.body.Offset == size
m.body1.Offset+m.body2.Offset == size
} }
func (m *Message) lastByte() byte { func (m *Message) lastByte() byte {
size := int(m.words * messageWordSize) size := int(m.words * messageWordSize)
if size > len(m.body1.Bytes) { return m.body.Bytes[size-1]
size = size - m.body1.Offset
return m.body2.Bytes[size-1]
}
return m.body1.Bytes[size-1]
} }
func (m *Message) bufferForGet() *buffer { func (m *Message) bufferForGet() *buffer {
size := int(m.words * messageWordSize) size := int(m.words * messageWordSize)
if m.body1.Offset == size || m.body1.Offset == len(m.body1.Bytes) { // The static body has been exahusted, use the dynamic one.
// The static body has been exahusted, use the dynamic one. if m.body.Offset == size {
if m.body1.Offset+m.body2.Offset == size { err := fmt.Errorf("short message: type=%d words=%d off=%d", m.mtype, m.words, m.body.Offset)
err := fmt.Errorf("short message: type=%d words=%d off=%d", m.mtype, m.words, m.body1.Offset) panic(err)
panic(err)
}
return &m.body2
} }
return &m.body1 return &m.body
} }
// Result holds the result of a statement. // Result holds the result of a statement.
...@@ -639,7 +575,7 @@ func (r *Rows) Close() error { ...@@ -639,7 +575,7 @@ func (r *Rows) Close() error {
err = fmt.Errorf("unexpected end of message") err = fmt.Errorf("unexpected end of message")
} }
} }
r.message.Reset() r.message.reset()
return err return err
} }
...@@ -664,7 +600,7 @@ func (f *Files) Next() (string, []byte) { ...@@ -664,7 +600,7 @@ func (f *Files) Next() (string, []byte) {
} }
func (f *Files) Close() { func (f *Files) Close() {
f.message.Reset() f.message.reset()
} }
const ( const (
......
...@@ -13,31 +13,23 @@ import ( ...@@ -13,31 +13,23 @@ import (
// Protocol sends and receive the dqlite message on the wire. // Protocol sends and receive the dqlite message on the wire.
type Protocol struct { type Protocol struct {
version uint64 // Protocol version version uint64 // Protocol version
conn net.Conn // Underlying network connection. conn net.Conn // Underlying network connection.
contextTimeout time.Duration // Default context timeout. closeCh chan struct{} // Stops the heartbeat when the connection gets closed
closeCh chan struct{} // Stops the heartbeat when the connection gets closed mu sync.Mutex // Serialize requests
mu sync.Mutex // Serialize requests netErr error // A network error occurred
netErr error // A network error occurred
} }
func NewProtocol(version uint64, conn net.Conn) *Protocol { func newProtocol(version uint64, conn net.Conn) *Protocol {
protocol := &Protocol{ protocol := &Protocol{
version: version, version: version,
conn: conn, conn: conn,
closeCh: make(chan struct{}), closeCh: make(chan struct{}),
contextTimeout: 5 * time.Second,
} }
return protocol return protocol
} }
// SetContextTimeout sets the default context timeout when no deadline is
// provided.
func (p *Protocol) SetContextTimeout(timeout time.Duration) {
p.contextTimeout = timeout
}
// Call invokes a dqlite RPC, sending a request message and receiving a // Call invokes a dqlite RPC, sending a request message and receiving a
// response message. // response message.
func (p *Protocol) Call(ctx context.Context, request, response *Message) (err error) { func (p *Protocol) Call(ctx context.Context, request, response *Message) (err error) {
...@@ -50,21 +42,22 @@ func (p *Protocol) Call(ctx context.Context, request, response *Message) (err er ...@@ -50,21 +42,22 @@ func (p *Protocol) Call(ctx context.Context, request, response *Message) (err er
return p.netErr return p.netErr
} }
// Honor the ctx deadline, if present, or use a default. var budget time.Duration
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(p.contextTimeout)
}
p.conn.SetDeadline(deadline) // Honor the ctx deadline, if present.
if deadline, ok := ctx.Deadline(); ok {
p.conn.SetDeadline(deadline)
budget = time.Until(deadline)
defer p.conn.SetDeadline(time.Time{})
}
if err = p.send(request); err != nil { if err = p.send(request); err != nil {
err = errors.Wrap(err, "failed to send request") err = errors.Wrapf(err, "send request (budget=%s)", budget)
goto err goto err
} }
if err = p.recv(response); err != nil { if err = p.recv(response); err != nil {
err = errors.Wrap(err, "failed to receive response") err = errors.Wrapf(err, "receive response (budget=%s)", budget)
goto err goto err
} }
...@@ -91,14 +84,11 @@ func (p *Protocol) Interrupt(ctx context.Context, request *Message, response *Me ...@@ -91,14 +84,11 @@ func (p *Protocol) Interrupt(ctx context.Context, request *Message, response *Me
p.mu.Lock() p.mu.Lock()
defer p.mu.Unlock() defer p.mu.Unlock()
// Honor the ctx deadline, if present, or use a default. // Honor the ctx deadline, if present.
deadline, ok := ctx.Deadline() if deadline, ok := ctx.Deadline(); ok {
if !ok { p.conn.SetDeadline(deadline)
deadline = time.Now().Add(2 * time.Second) defer p.conn.SetDeadline(time.Time{})
} }
p.conn.SetDeadline(deadline)
defer request.Reset()
EncodeInterrupt(request, 0) EncodeInterrupt(request, 0)
...@@ -108,12 +98,10 @@ func (p *Protocol) Interrupt(ctx context.Context, request *Message, response *Me ...@@ -108,12 +98,10 @@ func (p *Protocol) Interrupt(ctx context.Context, request *Message, response *Me
for { for {
if err := p.recv(response); err != nil { if err := p.recv(response); err != nil {
response.Reset()
return errors.Wrap(err, "failed to receive response") return errors.Wrap(err, "failed to receive response")
} }
mtype, _ := response.getHeader() mtype, _ := response.getHeader()
response.Reset()
if mtype == ResponseEmpty { if mtype == ResponseEmpty {
break break
...@@ -155,7 +143,7 @@ func (p *Protocol) sendHeader(req *Message) error { ...@@ -155,7 +143,7 @@ func (p *Protocol) sendHeader(req *Message) error {
} }
func (p *Protocol) sendBody(req *Message) error { func (p *Protocol) sendBody(req *Message) error {
buf := req.body1.Bytes[:req.body1.Offset] buf := req.body.Bytes[:req.body.Offset]
n, err := p.conn.Write(buf) n, err := p.conn.Write(buf)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to send static body") return errors.Wrap(err, "failed to send static body")
...@@ -165,24 +153,12 @@ func (p *Protocol) sendBody(req *Message) error { ...@@ -165,24 +153,12 @@ func (p *Protocol) sendBody(req *Message) error {
return errors.Wrap(io.ErrShortWrite, "failed to write body") return errors.Wrap(io.ErrShortWrite, "failed to write body")
} }
if req.body2.Bytes == nil {
return nil
}
buf = req.body2.Bytes[:req.body2.Offset]
n, err = p.conn.Write(buf)
if err != nil {
return errors.Wrap(err, "failed to send dynamic body")
}
if n != len(buf) {
return errors.Wrap(io.ErrShortWrite, "failed to write body")
}
return nil return nil
} }
func (p *Protocol) recv(res *Message) error { func (p *Protocol) recv(res *Message) error {
res.reset()
if err := p.recvHeader(res); err != nil { if err := p.recvHeader(res); err != nil {
return errors.Wrap(err, "failed to receive header") return errors.Wrap(err, "failed to receive header")
} }
...@@ -209,30 +185,19 @@ func (p *Protocol) recvHeader(res *Message) error { ...@@ -209,30 +185,19 @@ func (p *Protocol) recvHeader(res *Message) error {
func (p *Protocol) recvBody(res *Message) error { func (p *Protocol) recvBody(res *Message) error {
n := int(res.words) * messageWordSize n := int(res.words) * messageWordSize
n1 := n
n2 := 0
if n1 > len(res.body1.Bytes) { for n > len(res.body.Bytes) {
// We need to allocate the dynamic buffer. // Grow message buffer.
n1 = len(res.body1.Bytes) bytes := make([]byte, len(res.body.Bytes)*2)
n2 = n - n1 res.body.Bytes = bytes
} }
buf := res.body1.Bytes[:n1] buf := res.body.Bytes[:n]
if err := p.recvPeek(buf); err != nil { if err := p.recvPeek(buf); err != nil {
return errors.Wrap(err, "failed to read body") return errors.Wrap(err, "failed to read body")
} }
if n2 > 0 {
res.body2.Bytes = make([]byte, n2)
res.body2.Offset = 0
buf = res.body2.Bytes
if err := p.recvPeek(buf); err != nil {
return errors.Wrap(err, "failed to read body")
}
}
return nil return nil
} }
......
...@@ -7,6 +7,7 @@ package protocol ...@@ -7,6 +7,7 @@ package protocol
// EncodeLeader encodes a Leader request. // EncodeLeader encodes a Leader request.
func EncodeLeader(request *Message) { func EncodeLeader(request *Message) {
request.reset()
request.putUint64(0) request.putUint64(0)
request.putHeader(RequestLeader) request.putHeader(RequestLeader)
...@@ -14,6 +15,7 @@ func EncodeLeader(request *Message) { ...@@ -14,6 +15,7 @@ func EncodeLeader(request *Message) {
// EncodeClient encodes a Client request. // EncodeClient encodes a Client request.
func EncodeClient(request *Message, id uint64) { func EncodeClient(request *Message, id uint64) {
request.reset()
request.putUint64(id) request.putUint64(id)
request.putHeader(RequestClient) request.putHeader(RequestClient)
...@@ -21,6 +23,7 @@ func EncodeClient(request *Message, id uint64) { ...@@ -21,6 +23,7 @@ func EncodeClient(request *Message, id uint64) {
// EncodeHeartbeat encodes a Heartbeat request. // EncodeHeartbeat encodes a Heartbeat request.
func EncodeHeartbeat(request *Message, timestamp uint64) { func EncodeHeartbeat(request *Message, timestamp uint64) {
request.reset()
request.putUint64(timestamp) request.putUint64(timestamp)
request.putHeader(RequestHeartbeat) request.putHeader(RequestHeartbeat)
...@@ -28,6 +31,7 @@ func EncodeHeartbeat(request *Message, timestamp uint64) { ...@@ -28,6 +31,7 @@ func EncodeHeartbeat(request *Message, timestamp uint64) {
// EncodeOpen encodes a Open request. // EncodeOpen encodes a Open request.
func EncodeOpen(request *Message, name string, flags uint64, vfs string) { func EncodeOpen(request *Message, name string, flags uint64, vfs string) {
request.reset()
request.putString(name) request.putString(name)
request.putUint64(flags) request.putUint64(flags)
request.putString(vfs) request.putString(vfs)
...@@ -37,6 +41,7 @@ func EncodeOpen(request *Message, name string, flags uint64, vfs string) { ...@@ -37,6 +41,7 @@ func EncodeOpen(request *Message, name string, flags uint64, vfs string) {
// EncodePrepare encodes a Prepare request. // EncodePrepare encodes a Prepare request.
func EncodePrepare(request *Message, db uint64, sql string) { func EncodePrepare(request *Message, db uint64, sql string) {
request.reset()
request.putUint64(db) request.putUint64(db)
request.putString(sql) request.putString(sql)
...@@ -45,6 +50,7 @@ func EncodePrepare(request *Message, db uint64, sql string) { ...@@ -45,6 +50,7 @@ func EncodePrepare(request *Message, db uint64, sql string) {
// EncodeExec encodes a Exec request. // EncodeExec encodes a Exec request.
func EncodeExec(request *Message, db uint32, stmt uint32, values NamedValues) { func EncodeExec(request *Message, db uint32, stmt uint32, values NamedValues) {
request.reset()
request.putUint32(db) request.putUint32(db)
request.putUint32(stmt) request.putUint32(stmt)
request.putNamedValues(values) request.putNamedValues(values)
...@@ -54,6 +60,7 @@ func EncodeExec(request *Message, db uint32, stmt uint32, values NamedValues) { ...@@ -54,6 +60,7 @@ func EncodeExec(request *Message, db uint32, stmt uint32, values NamedValues) {
// EncodeQuery encodes a Query request. // EncodeQuery encodes a Query request.
func EncodeQuery(request *Message, db uint32, stmt uint32, values NamedValues) { func EncodeQuery(request *Message, db uint32, stmt uint32, values NamedValues) {
request.reset()
request.putUint32(db) request.putUint32(db)
request.putUint32(stmt) request.putUint32(stmt)
request.putNamedValues(values) request.putNamedValues(values)
...@@ -63,6 +70,7 @@ func EncodeQuery(request *Message, db uint32, stmt uint32, values NamedValues) { ...@@ -63,6 +70,7 @@ func EncodeQuery(request *Message, db uint32, stmt uint32, values NamedValues) {
// EncodeFinalize encodes a Finalize request. // EncodeFinalize encodes a Finalize request.
func EncodeFinalize(request *Message, db uint32, stmt uint32) { func EncodeFinalize(request *Message, db uint32, stmt uint32) {
request.reset()
request.putUint32(db) request.putUint32(db)
request.putUint32(stmt) request.putUint32(stmt)
...@@ -71,6 +79,7 @@ func EncodeFinalize(request *Message, db uint32, stmt uint32) { ...@@ -71,6 +79,7 @@ func EncodeFinalize(request *Message, db uint32, stmt uint32) {
// EncodeExecSQL encodes a ExecSQL request. // EncodeExecSQL encodes a ExecSQL request.
func EncodeExecSQL(request *Message, db uint64, sql string, values NamedValues) { func EncodeExecSQL(request *Message, db uint64, sql string, values NamedValues) {
request.reset()
request.putUint64(db) request.putUint64(db)
request.putString(sql) request.putString(sql)
request.putNamedValues(values) request.putNamedValues(values)
...@@ -80,6 +89,7 @@ func EncodeExecSQL(request *Message, db uint64, sql string, values NamedValues) ...@@ -80,6 +89,7 @@ func EncodeExecSQL(request *Message, db uint64, sql string, values NamedValues)
// EncodeQuerySQL encodes a QuerySQL request. // EncodeQuerySQL encodes a QuerySQL request.
func EncodeQuerySQL(request *Message, db uint64, sql string, values NamedValues) { func EncodeQuerySQL(request *Message, db uint64, sql string, values NamedValues) {
request.reset()
request.putUint64(db) request.putUint64(db)
request.putString(sql) request.putString(sql)
request.putNamedValues(values) request.putNamedValues(values)
...@@ -89,6 +99,7 @@ func EncodeQuerySQL(request *Message, db uint64, sql string, values NamedValues) ...@@ -89,6 +99,7 @@ func EncodeQuerySQL(request *Message, db uint64, sql string, values NamedValues)
// EncodeInterrupt encodes a Interrupt request. // EncodeInterrupt encodes a Interrupt request.
func EncodeInterrupt(request *Message, db uint64) { func EncodeInterrupt(request *Message, db uint64) {
request.reset()
request.putUint64(db) request.putUint64(db)
request.putHeader(RequestInterrupt) request.putHeader(RequestInterrupt)
...@@ -96,6 +107,7 @@ func EncodeInterrupt(request *Message, db uint64) { ...@@ -96,6 +107,7 @@ func EncodeInterrupt(request *Message, db uint64) {
// EncodeAdd encodes a Add request. // EncodeAdd encodes a Add request.
func EncodeAdd(request *Message, id uint64, address string) { func EncodeAdd(request *Message, id uint64, address string) {
request.reset()
request.putUint64(id) request.putUint64(id)
request.putString(address) request.putString(address)
...@@ -104,6 +116,7 @@ func EncodeAdd(request *Message, id uint64, address string) { ...@@ -104,6 +116,7 @@ func EncodeAdd(request *Message, id uint64, address string) {
// EncodeAssign encodes a Assign request. // EncodeAssign encodes a Assign request.
func EncodeAssign(request *Message, id uint64, role uint64) { func EncodeAssign(request *Message, id uint64, role uint64) {
request.reset()
request.putUint64(id) request.putUint64(id)
request.putUint64(role) request.putUint64(role)
...@@ -112,6 +125,7 @@ func EncodeAssign(request *Message, id uint64, role uint64) { ...@@ -112,6 +125,7 @@ func EncodeAssign(request *Message, id uint64, role uint64) {
// EncodeRemove encodes a Remove request. // EncodeRemove encodes a Remove request.
func EncodeRemove(request *Message, id uint64) { func EncodeRemove(request *Message, id uint64) {
request.reset()
request.putUint64(id) request.putUint64(id)
request.putHeader(RequestRemove) request.putHeader(RequestRemove)
...@@ -119,6 +133,7 @@ func EncodeRemove(request *Message, id uint64) { ...@@ -119,6 +133,7 @@ func EncodeRemove(request *Message, id uint64) {
// EncodeDump encodes a Dump request. // EncodeDump encodes a Dump request.
func EncodeDump(request *Message, name string) { func EncodeDump(request *Message, name string) {
request.reset()
request.putString(name) request.putString(name)
request.putHeader(RequestDump) request.putHeader(RequestDump)
...@@ -126,6 +141,7 @@ func EncodeDump(request *Message, name string) { ...@@ -126,6 +141,7 @@ func EncodeDump(request *Message, name string) {
// EncodeCluster encodes a Cluster request. // EncodeCluster encodes a Cluster request.
func EncodeCluster(request *Message, format uint64) { func EncodeCluster(request *Message, format uint64) {
request.reset()
request.putUint64(format) request.putUint64(format)
request.putHeader(RequestCluster) request.putHeader(RequestCluster)
...@@ -133,6 +149,7 @@ func EncodeCluster(request *Message, format uint64) { ...@@ -133,6 +149,7 @@ func EncodeCluster(request *Message, format uint64) {
// EncodeTransfer encodes a Transfer request. // EncodeTransfer encodes a Transfer request.
func EncodeTransfer(request *Message, id uint64) { func EncodeTransfer(request *Message, id uint64) {
request.reset()
request.putUint64(id) request.putUint64(id)
request.putHeader(RequestTransfer) request.putHeader(RequestTransfer)
......
...@@ -54,6 +54,7 @@ if [ "$entity" = "--request" ]; then ...@@ -54,6 +54,7 @@ if [ "$entity" = "--request" ]; then
// Encode${cmd} encodes a $cmd request. // Encode${cmd} encodes a $cmd request.
func Encode${cmd}(request *Message${args}) { func Encode${cmd}(request *Message${args}) {
request.reset()
EOF EOF
for i in "${@}" for i in "${@}"
......
...@@ -118,6 +118,16 @@ func (s *Node) Close() error { ...@@ -118,6 +118,16 @@ func (s *Node) Close() error {
return nil return nil
} }
// BootstrapID is a magic ID that should be used for the fist node in a
// cluster. Alternatively ID 1 can be used as well.
const BootstrapID = 0x2dc171858c3155be
// GenerateID generates a unique ID for a new node, based on a hash of its
// address and the current time.
func GenerateID(address string) uint64 {
return bindings.GenerateID(address)
}
// Create a options object with sane defaults. // Create a options object with sane defaults.
func defaultOptions() *options { func defaultOptions() *options {
return &options{ return &options{
......
...@@ -131,7 +131,7 @@ github.com/blang/semver ...@@ -131,7 +131,7 @@ github.com/blang/semver
github.com/bronze1man/goStrongswanVici github.com/bronze1man/goStrongswanVici
# github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 # github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23
github.com/buger/jsonparser github.com/buger/jsonparser
# github.com/canonical/go-dqlite v1.3.0 # github.com/canonical/go-dqlite v1.5.1
github.com/canonical/go-dqlite github.com/canonical/go-dqlite
github.com/canonical/go-dqlite/client github.com/canonical/go-dqlite/client
github.com/canonical/go-dqlite/driver github.com/canonical/go-dqlite/driver
...@@ -726,7 +726,7 @@ github.com/rancher/helm-controller/pkg/generated/informers/externalversions/helm ...@@ -726,7 +726,7 @@ github.com/rancher/helm-controller/pkg/generated/informers/externalversions/helm
github.com/rancher/helm-controller/pkg/generated/informers/externalversions/internalinterfaces github.com/rancher/helm-controller/pkg/generated/informers/externalversions/internalinterfaces
github.com/rancher/helm-controller/pkg/generated/listers/helm.cattle.io/v1 github.com/rancher/helm-controller/pkg/generated/listers/helm.cattle.io/v1
github.com/rancher/helm-controller/pkg/helm github.com/rancher/helm-controller/pkg/helm
# github.com/rancher/kine v0.3.6-0.20200422224205-0a0f5b924129 # github.com/rancher/kine v0.4.0
github.com/rancher/kine/pkg/broadcaster github.com/rancher/kine/pkg/broadcaster
github.com/rancher/kine/pkg/client github.com/rancher/kine/pkg/client
github.com/rancher/kine/pkg/drivers/dqlite github.com/rancher/kine/pkg/drivers/dqlite
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment