mirror of https://github.com/tidwall/tile38.git
294 lines
6.9 KiB
Go
294 lines
6.9 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"
|
|
)
|
|
|
|
var validProperties = []string{RequirePass, LeaderAuth, ProtectedMode, MaxMemory, AutoGC}
|
|
|
|
// 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:"-"`
|
|
}
|
|
|
|
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
|
|
}
|
|
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
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
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
|
|
}
|