
Enabled TCP keepalive packets to determine if the connection is still valid, and terminate if needed. It also helps with maintaining idle connections. Default to 300 seconds and can be changed by: CONFIG SET keepalive 300 addresses #145: clients not being cleaned up properly
315 lines
7.5 KiB
Go
315 lines
7.5 KiB
Go
package controller
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/tidwall/resp"
|
|
"github.com/tidwall/tile38/controller/glob"
|
|
"github.com/tidwall/tile38/controller/server"
|
|
)
|
|
|
|
const (
|
|
RequirePass = "requirepass"
|
|
LeaderAuth = "leaderauth"
|
|
ProtectedMode = "protected-mode"
|
|
MaxMemory = "maxmemory"
|
|
AutoGC = "autogc"
|
|
KeepAlive = "keepalive"
|
|
)
|
|
|
|
var validProperties = []string{RequirePass, LeaderAuth, ProtectedMode, MaxMemory, AutoGC, KeepAlive}
|
|
|
|
// Config is a tile38 config
|
|
type Config struct {
|
|
FollowHost string `json:"follow_host,omitempty"`
|
|
FollowPort int `json:"follow_port,omitempty"`
|
|
FollowID string `json:"follow_id,omitempty"`
|
|
FollowPos int `json:"follow_pos,omitempty"`
|
|
ServerID string `json:"server_id,omitempty"`
|
|
ReadOnly bool `json:"read_only,omitempty"`
|
|
|
|
// Properties
|
|
RequirePassP string `json:"requirepass,omitempty"`
|
|
RequirePass string `json:"-"`
|
|
LeaderAuthP string `json:"leaderauth,omitempty"`
|
|
LeaderAuth string `json:"-"`
|
|
ProtectedModeP string `json:"protected-mode,omitempty"`
|
|
ProtectedMode string `json:"-"`
|
|
MaxMemoryP string `json:"maxmemory,omitempty"`
|
|
MaxMemory int `json:"-"`
|
|
AutoGCP string `json:"autogc,omitempty"`
|
|
AutoGC uint64 `json:"-"`
|
|
KeepAliveP string `json:"keepalive,omitempty"`
|
|
KeepAlive int `json:"-"`
|
|
}
|
|
|
|
func (c *Controller) loadConfig() error {
|
|
data, err := ioutil.ReadFile(c.dir + "/config")
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return c.initConfig()
|
|
}
|
|
return err
|
|
}
|
|
err = json.Unmarshal(data, &c.config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// load properties
|
|
if err := c.setConfigProperty(RequirePass, c.config.RequirePassP, true); err != nil {
|
|
return err
|
|
}
|
|
if err := c.setConfigProperty(LeaderAuth, c.config.LeaderAuthP, true); err != nil {
|
|
return err
|
|
}
|
|
if err := c.setConfigProperty(ProtectedMode, c.config.ProtectedModeP, true); err != nil {
|
|
return err
|
|
}
|
|
if err := c.setConfigProperty(MaxMemory, c.config.MaxMemoryP, true); err != nil {
|
|
return err
|
|
}
|
|
if err := c.setConfigProperty(AutoGC, c.config.AutoGCP, true); err != nil {
|
|
return err
|
|
}
|
|
if err := c.setConfigProperty(KeepAlive, c.config.KeepAliveP, true); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseMemSize(s string) (bytes int, ok bool) {
|
|
if s == "" {
|
|
return 0, true
|
|
}
|
|
s = strings.ToLower(s)
|
|
var n uint64
|
|
var sz int
|
|
var err error
|
|
if strings.HasSuffix(s, "gb") {
|
|
n, err = strconv.ParseUint(s[:len(s)-2], 10, 64)
|
|
sz = int(n * 1024 * 1024 * 1024)
|
|
} else if strings.HasSuffix(s, "mb") {
|
|
n, err = strconv.ParseUint(s[:len(s)-2], 10, 64)
|
|
sz = int(n * 1024 * 1024)
|
|
} else if strings.HasSuffix(s, "kb") {
|
|
n, err = strconv.ParseUint(s[:len(s)-2], 10, 64)
|
|
sz = int(n * 1024)
|
|
} else {
|
|
n, err = strconv.ParseUint(s, 10, 64)
|
|
sz = int(n)
|
|
}
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
return sz, true
|
|
}
|
|
|
|
func formatMemSize(sz int) string {
|
|
if sz <= 0 {
|
|
return ""
|
|
}
|
|
if sz < 1024 {
|
|
return strconv.FormatInt(int64(sz), 10)
|
|
}
|
|
sz /= 1024
|
|
if sz < 1024 {
|
|
return strconv.FormatInt(int64(sz), 10) + "kb"
|
|
}
|
|
sz /= 1024
|
|
if sz < 1024 {
|
|
return strconv.FormatInt(int64(sz), 10) + "mb"
|
|
}
|
|
sz /= 1024
|
|
return strconv.FormatInt(int64(sz), 10) + "gb"
|
|
}
|
|
|
|
func (c *Controller) setConfigProperty(name, value string, fromLoad bool) error {
|
|
var invalid bool
|
|
switch name {
|
|
default:
|
|
return fmt.Errorf("Unsupported CONFIG parameter: %s", name)
|
|
case RequirePass:
|
|
c.config.RequirePass = value
|
|
case LeaderAuth:
|
|
c.config.LeaderAuth = value
|
|
case AutoGC:
|
|
if value == "" {
|
|
c.config.AutoGC = 0
|
|
} else {
|
|
gc, err := strconv.ParseUint(value, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.config.AutoGC = gc
|
|
}
|
|
case MaxMemory:
|
|
sz, ok := parseMemSize(value)
|
|
if !ok {
|
|
return fmt.Errorf("Invalid argument '%s' for CONFIG SET '%s'", value, name)
|
|
}
|
|
c.config.MaxMemory = sz
|
|
case ProtectedMode:
|
|
switch strings.ToLower(value) {
|
|
case "":
|
|
if fromLoad {
|
|
c.config.ProtectedMode = "yes"
|
|
} else {
|
|
invalid = true
|
|
}
|
|
case "yes", "no":
|
|
c.config.ProtectedMode = strings.ToLower(value)
|
|
default:
|
|
invalid = true
|
|
}
|
|
case KeepAlive:
|
|
if value == "" {
|
|
c.config.KeepAlive = 300
|
|
} else {
|
|
keepalive, err := strconv.ParseUint(value, 10, 64)
|
|
if err != nil {
|
|
invalid = true
|
|
} else {
|
|
c.config.KeepAlive = int(keepalive)
|
|
}
|
|
}
|
|
}
|
|
|
|
if invalid {
|
|
return fmt.Errorf("Invalid argument '%s' for CONFIG SET '%s'", value, name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Controller) getConfigProperties(pattern string) map[string]interface{} {
|
|
m := make(map[string]interface{})
|
|
for _, name := range validProperties {
|
|
matched, _ := glob.Match(pattern, name)
|
|
if matched {
|
|
m[name] = c.getConfigProperty(name)
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
func (c *Controller) getConfigProperty(name string) string {
|
|
switch name {
|
|
default:
|
|
return ""
|
|
case AutoGC:
|
|
return strconv.FormatUint(c.config.AutoGC, 10)
|
|
case RequirePass:
|
|
return c.config.RequirePass
|
|
case LeaderAuth:
|
|
return c.config.LeaderAuth
|
|
case ProtectedMode:
|
|
return c.config.ProtectedMode
|
|
case MaxMemory:
|
|
return formatMemSize(c.config.MaxMemory)
|
|
case KeepAlive:
|
|
return strconv.FormatUint(uint64(c.config.KeepAlive), 10)
|
|
}
|
|
}
|
|
|
|
func (c *Controller) initConfig() error {
|
|
c.config = Config{ServerID: randomKey(16)}
|
|
return c.writeConfig(true)
|
|
}
|
|
|
|
func (c *Controller) writeConfig(writeProperties bool) error {
|
|
var err error
|
|
bak := c.config
|
|
defer func() {
|
|
if err != nil {
|
|
// revert changes
|
|
c.config = bak
|
|
}
|
|
}()
|
|
if writeProperties {
|
|
// save properties
|
|
c.config.RequirePassP = c.config.RequirePass
|
|
c.config.LeaderAuthP = c.config.LeaderAuth
|
|
c.config.ProtectedModeP = c.config.ProtectedMode
|
|
c.config.MaxMemoryP = formatMemSize(c.config.MaxMemory)
|
|
c.config.AutoGCP = strconv.FormatUint(c.config.AutoGC, 10)
|
|
c.config.KeepAliveP = strconv.FormatUint(uint64(c.config.KeepAlive), 10)
|
|
}
|
|
var data []byte
|
|
data, err = json.MarshalIndent(c.config, "", "\t")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = ioutil.WriteFile(c.dir+"/config", data, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Controller) cmdConfigGet(msg *server.Message) (res string, err error) {
|
|
start := time.Now()
|
|
vs := msg.Values[1:]
|
|
var ok bool
|
|
var name string
|
|
if vs, name, ok = tokenval(vs); !ok {
|
|
return "", errInvalidNumberOfArguments
|
|
}
|
|
if len(vs) != 0 {
|
|
return "", errInvalidNumberOfArguments
|
|
}
|
|
m := c.getConfigProperties(name)
|
|
switch msg.OutputType {
|
|
case server.JSON:
|
|
data, err := json.Marshal(m)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
res = `{"ok":true,"properties":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}"
|
|
case server.RESP:
|
|
vals := respValuesSimpleMap(m)
|
|
data, err := resp.ArrayValue(vals).MarshalRESP()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
res = string(data)
|
|
}
|
|
return
|
|
}
|
|
func (c *Controller) cmdConfigSet(msg *server.Message) (res string, err error) {
|
|
start := time.Now()
|
|
vs := msg.Values[1:]
|
|
var ok bool
|
|
var name string
|
|
if vs, name, ok = tokenval(vs); !ok {
|
|
return "", errInvalidNumberOfArguments
|
|
}
|
|
var value string
|
|
if vs, value, ok = tokenval(vs); !ok {
|
|
if strings.ToLower(name) != RequirePass {
|
|
return "", errInvalidNumberOfArguments
|
|
}
|
|
}
|
|
if len(vs) != 0 {
|
|
return "", errInvalidNumberOfArguments
|
|
}
|
|
if err := c.setConfigProperty(name, value, false); err != nil {
|
|
return "", err
|
|
}
|
|
return server.OKMessage(msg, start), nil
|
|
}
|
|
func (c *Controller) cmdConfigRewrite(msg *server.Message) (res string, err error) {
|
|
start := time.Now()
|
|
vs := msg.Values[1:]
|
|
if len(vs) != 0 {
|
|
return "", errInvalidNumberOfArguments
|
|
}
|
|
if err := c.writeConfig(true); err != nil {
|
|
return "", err
|
|
}
|
|
return server.OKMessage(msg, start), nil
|
|
}
|