tile38/internal/server/follow.go

317 lines
7.0 KiB
Go
Raw Permalink Normal View History

package server
2016-03-05 02:08:16 +03:00
import (
"errors"
"fmt"
"io"
"io/ioutil"
"strconv"
"strings"
"time"
2016-04-01 02:26:36 +03:00
"github.com/tidwall/resp"
"github.com/tidwall/tile38/core"
"github.com/tidwall/tile38/internal/log"
2016-03-05 02:08:16 +03:00
)
var errNoLongerFollowing = errors.New("no longer following")
const checksumsz = 512 * 1024
func (c *Server) cmdFollow(msg *Message) (res resp.Value, err error) {
2016-04-01 02:26:36 +03:00
start := time.Now()
vs := msg.Args[1:]
2016-04-01 02:26:36 +03:00
var ok bool
2016-03-05 02:08:16 +03:00
var host, sport string
2017-10-05 18:20:40 +03:00
2016-04-01 02:26:36 +03:00
if vs, host, ok = tokenval(vs); !ok || host == "" {
return NOMessage, errInvalidNumberOfArguments
2016-03-05 02:08:16 +03:00
}
2016-04-01 02:26:36 +03:00
if vs, sport, ok = tokenval(vs); !ok || sport == "" {
return NOMessage, errInvalidNumberOfArguments
2016-03-05 02:08:16 +03:00
}
2016-04-01 02:26:36 +03:00
if len(vs) != 0 {
return NOMessage, errInvalidNumberOfArguments
2016-03-05 02:08:16 +03:00
}
host = strings.ToLower(host)
sport = strings.ToLower(sport)
var update bool
if host == "no" && sport == "one" {
2017-09-30 04:11:05 +03:00
update = c.config.followHost() != "" || c.config.followPort() != 0
c.config.setFollowHost("")
c.config.setFollowPort(0)
2016-03-05 02:08:16 +03:00
} else {
n, err := strconv.ParseUint(sport, 10, 64)
if err != nil {
return NOMessage, errInvalidArgument(sport)
2016-03-05 02:08:16 +03:00
}
port := int(n)
2017-09-30 04:11:05 +03:00
update = c.config.followHost() != host || c.config.followPort() != port
auth := c.config.leaderAuth()
2016-03-05 02:08:16 +03:00
if update {
c.mu.Unlock()
2016-04-01 02:26:36 +03:00
conn, err := DialTimeout(fmt.Sprintf("%s:%d", host, port), time.Second*2)
2016-03-05 02:08:16 +03:00
if err != nil {
c.mu.Lock()
return NOMessage, fmt.Errorf("cannot follow: %v", err)
2016-03-05 02:08:16 +03:00
}
defer conn.Close()
2016-03-08 18:35:43 +03:00
if auth != "" {
if err := c.followDoLeaderAuth(conn, auth); err != nil {
return NOMessage, fmt.Errorf("cannot follow: %v", err)
2016-03-08 18:35:43 +03:00
}
}
2016-04-01 02:26:36 +03:00
m, err := doServer(conn)
2016-03-05 02:08:16 +03:00
if err != nil {
c.mu.Lock()
return NOMessage, fmt.Errorf("cannot follow: %v", err)
2016-03-05 02:08:16 +03:00
}
2016-04-01 02:26:36 +03:00
if m["id"] == "" {
2016-03-05 02:08:16 +03:00
c.mu.Lock()
return NOMessage, fmt.Errorf("cannot follow: invalid id")
2016-03-05 02:08:16 +03:00
}
2017-09-30 04:11:05 +03:00
if m["id"] == c.config.serverID() {
2016-03-05 02:08:16 +03:00
c.mu.Lock()
return NOMessage, fmt.Errorf("cannot follow self")
2016-04-01 02:26:36 +03:00
}
if m["following"] != "" {
c.mu.Lock()
return NOMessage, fmt.Errorf("cannot follow a follower")
2016-03-05 02:08:16 +03:00
}
c.mu.Lock()
}
2017-09-30 04:11:05 +03:00
c.config.setFollowHost(host)
c.config.setFollowPort(port)
2016-03-05 02:08:16 +03:00
}
2017-09-30 04:11:05 +03:00
c.config.write(false)
2016-03-05 02:08:16 +03:00
if update {
2017-09-30 17:34:08 +03:00
c.followc.add(1)
2017-09-30 04:11:05 +03:00
if c.config.followHost() != "" {
2016-03-05 02:08:16 +03:00
log.Infof("following new host '%s' '%s'.", host, sport)
2017-09-30 17:34:08 +03:00
go c.follow(c.config.followHost(), c.config.followPort(), c.followc.get())
2016-03-05 02:08:16 +03:00
} else {
log.Infof("following no one")
}
}
return OKMessage(msg, start), nil
2016-04-01 02:26:36 +03:00
}
// cmdReplConf is a command handler that sets replication configuration info
func (c *Server) cmdReplConf(msg *Message, client *Client) (res resp.Value, err error) {
start := time.Now()
vs := msg.Args[1:]
var ok bool
var cmd, val string
// Parse the message
if vs, cmd, ok = tokenval(vs); !ok || cmd == "" {
return NOMessage, errInvalidNumberOfArguments
}
if vs, val, ok = tokenval(vs); !ok || val == "" {
return NOMessage, errInvalidNumberOfArguments
}
// Switch on the command received
switch cmd {
case "listening-port":
// Parse the port as an integer
port, err := strconv.Atoi(val)
if err != nil {
return NOMessage, errInvalidArgument(val)
}
// Apply the replication port to the client and return
for _, c := range c.conns {
if c.remoteAddr == client.remoteAddr {
c.replPort = port
return OKMessage(msg, start), nil
}
}
}
return NOMessage, fmt.Errorf("cannot find follower")
}
func doServer(conn *RESPConn) (map[string]string, error) {
2016-04-01 02:26:36 +03:00
v, err := conn.Do("server")
if err != nil {
return nil, err
}
if v.Error() != nil {
return nil, v.Error()
}
arr := v.Array()
m := make(map[string]string)
for i := 0; i < len(arr)/2; i++ {
m[arr[i*2+0].String()] = arr[i*2+1].String()
}
return m, err
2016-03-05 02:08:16 +03:00
}
func (c *Server) followHandleCommand(args []string, followc int, w io.Writer) (int, error) {
2016-03-05 02:08:16 +03:00
c.mu.Lock()
defer c.mu.Unlock()
2017-09-30 17:34:08 +03:00
if c.followc.get() != followc {
2016-04-01 02:26:36 +03:00
return c.aofsz, errNoLongerFollowing
}
msg := &Message{Args: args}
_, d, err := c.command(msg, nil)
2016-04-01 02:26:36 +03:00
if err != nil {
if commandErrIsFatal(err) {
return c.aofsz, err
}
}
if err := c.writeAOF(args, &d); err != nil {
2016-04-01 02:26:36 +03:00
return c.aofsz, err
}
2018-11-13 22:04:16 +03:00
if len(c.aofbuf) > 10240 {
2019-03-10 20:48:14 +03:00
c.flushAOF(false)
2018-11-13 22:04:16 +03:00
}
2016-03-05 02:08:16 +03:00
return c.aofsz, nil
}
func (c *Server) followDoLeaderAuth(conn *RESPConn, auth string) error {
2016-04-01 02:26:36 +03:00
v, err := conn.Do("auth", auth)
2016-03-08 18:35:43 +03:00
if err != nil {
return err
}
2016-04-01 02:26:36 +03:00
if v.Error() != nil {
return v.Error()
2016-03-08 18:35:43 +03:00
}
2016-04-01 02:26:36 +03:00
if v.String() != "OK" {
return errors.New("cannot follow: auth no ok")
2016-03-08 18:35:43 +03:00
}
return nil
}
func (c *Server) followStep(host string, port int, followc int) error {
2017-09-30 17:34:08 +03:00
if c.followc.get() != followc {
2016-03-05 02:08:16 +03:00
return errNoLongerFollowing
}
2017-09-30 17:34:08 +03:00
c.mu.Lock()
2016-03-05 02:08:16 +03:00
c.fcup = false
2017-09-30 04:11:05 +03:00
auth := c.config.leaderAuth()
2016-03-05 02:08:16 +03:00
c.mu.Unlock()
addr := fmt.Sprintf("%s:%d", host, port)
2016-04-01 02:26:36 +03:00
2016-03-05 02:08:16 +03:00
// check if we are following self
2016-04-01 02:26:36 +03:00
conn, err := DialTimeout(addr, time.Second*2)
2016-03-05 02:08:16 +03:00
if err != nil {
return fmt.Errorf("cannot follow: %v", err)
}
defer conn.Close()
2016-03-08 18:35:43 +03:00
if auth != "" {
if err := c.followDoLeaderAuth(conn, auth); err != nil {
return fmt.Errorf("cannot follow: %v", err)
}
}
2016-04-01 02:26:36 +03:00
m, err := doServer(conn)
2016-03-05 02:08:16 +03:00
if err != nil {
return fmt.Errorf("cannot follow: %v", err)
}
2016-04-01 02:26:36 +03:00
if m["id"] == "" {
return fmt.Errorf("cannot follow: invalid id")
}
2017-09-30 04:11:05 +03:00
if m["id"] == c.config.serverID() {
2016-03-05 02:08:16 +03:00
return fmt.Errorf("cannot follow self")
}
2016-04-01 02:26:36 +03:00
if m["following"] != "" {
2016-03-05 02:08:16 +03:00
return fmt.Errorf("cannot follow a follower")
}
2016-04-01 02:26:36 +03:00
2016-03-05 02:08:16 +03:00
// verify checksum
pos, err := c.followCheckSome(addr, followc)
if err != nil {
return err
}
// Send the replication port to the leader
v, err := conn.Do("replconf", "listening-port", c.port)
if err != nil {
return err
}
if v.Error() != nil {
return v.Error()
}
if v.String() != "OK" {
return errors.New("invalid response to replconf request")
}
if core.ShowDebugMessages {
log.Debug("follow:", addr, ":replconf")
}
v, err = conn.Do("aof", pos)
2016-03-05 02:08:16 +03:00
if err != nil {
return err
}
2016-04-01 02:26:36 +03:00
if v.Error() != nil {
return v.Error()
}
if v.String() != "OK" {
2016-03-05 02:08:16 +03:00
return errors.New("invalid response to aof live request")
}
2016-03-06 17:55:00 +03:00
if core.ShowDebugMessages {
2016-03-05 02:08:16 +03:00
log.Debug("follow:", addr, ":read aof")
}
2016-04-01 02:26:36 +03:00
aofSize, err := strconv.ParseInt(m["aof_size"], 10, 64)
if err != nil {
return err
}
caughtUp := pos >= aofSize
2016-03-05 02:08:16 +03:00
if caughtUp {
c.mu.Lock()
c.fcup = true
c.fcuponce = true
2016-03-05 02:08:16 +03:00
c.mu.Unlock()
log.Info("caught up")
}
nullw := ioutil.Discard
for {
2016-04-01 02:26:36 +03:00
v, telnet, _, err := conn.rd.ReadMultiBulk()
2016-03-05 02:08:16 +03:00
if err != nil {
return err
}
2016-04-01 02:26:36 +03:00
vals := v.Array()
if telnet || v.Type() != resp.Array {
return errors.New("invalid multibulk")
}
svals := make([]string, len(vals))
for i := 0; i < len(vals); i++ {
svals[i] = vals[i].String()
}
2016-04-01 03:58:02 +03:00
aofsz, err := c.followHandleCommand(svals, followc, nullw)
2016-03-05 02:08:16 +03:00
if err != nil {
return err
}
if !caughtUp {
2016-04-01 02:26:36 +03:00
if aofsz >= int(aofSize) {
2016-03-05 02:08:16 +03:00
caughtUp = true
c.mu.Lock()
2019-03-10 20:48:14 +03:00
c.flushAOF(false)
2016-03-05 02:08:16 +03:00
c.fcup = true
c.fcuponce = true
2016-03-05 02:08:16 +03:00
c.mu.Unlock()
log.Info("caught up")
}
}
}
}
func (c *Server) follow(host string, port int, followc int) {
2016-03-05 02:08:16 +03:00
for {
err := c.followStep(host, port, followc)
if err == errNoLongerFollowing {
return
}
if err != nil && err != io.EOF {
2016-03-08 18:35:43 +03:00
log.Error("follow: " + err.Error())
2016-03-05 02:08:16 +03:00
}
time.Sleep(time.Second)
}
}