tile38/internal/server/config.go

463 lines
10 KiB
Go
Raw Permalink Normal View History

package server
2016-03-08 03:37:39 +03:00
import (
"encoding/json"
"io/ioutil"
"os"
2016-05-24 05:44:25 +03:00
"strconv"
2016-03-08 03:37:39 +03:00
"strings"
2017-09-30 04:11:05 +03:00
"sync"
2016-03-08 03:37:39 +03:00
"time"
2016-03-29 15:53:53 +03:00
2017-09-30 04:11:05 +03:00
"github.com/tidwall/gjson"
2016-03-29 15:53:53 +03:00
"github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/glob"
2016-03-08 03:37:39 +03:00
)
2017-08-25 00:15:07 +03:00
const (
2017-08-25 00:24:11 +03:00
defaultKeepAlive = 300 // seconds
defaultProtectedMode = "yes"
2017-08-25 00:15:07 +03:00
)
// Config keys
const (
2017-09-30 04:11:05 +03:00
FollowHost = "follow_host"
FollowPort = "follow_port"
FollowID = "follow_id"
FollowPos = "follow_pos"
ServerID = "server_id"
ReadOnly = "read_only"
RequirePass = "requirepass"
LeaderAuth = "leaderauth"
ProtectedMode = "protected-mode"
MaxMemory = "maxmemory"
AutoGC = "autogc"
KeepAlive = "keepalive"
)
var validProperties = []string{RequirePass, LeaderAuth, ProtectedMode, MaxMemory, AutoGC, KeepAlive}
2016-03-29 15:53:53 +03:00
2016-03-08 03:37:39 +03:00
// Config is a tile38 config
type Config struct {
2017-09-30 04:11:05 +03:00
path string
2016-03-08 03:37:39 +03:00
2017-09-30 04:11:05 +03:00
mu sync.RWMutex
_followHost string
_followPort int64
_followID string
_followPos int64
_serverID string
_readOnly bool
_requirePassP string
_requirePass string
_leaderAuthP string
_leaderAuth string
_protectedModeP string
_protectedMode string
_maxMemoryP string
_maxMemory int64
_autoGCP string
_autoGC uint64
_keepAliveP string
_keepAlive int64
2016-03-08 03:37:39 +03:00
}
2017-09-30 04:11:05 +03:00
func loadConfig(path string) (*Config, error) {
2017-09-30 16:56:52 +03:00
var json string
2017-09-30 04:11:05 +03:00
data, err := ioutil.ReadFile(path)
2016-03-08 03:37:39 +03:00
if err != nil {
if os.IsNotExist(err) {
2017-09-30 16:56:52 +03:00
json = `{"` + ServerID + `":"` + randomKey(16) + `"}`
} else {
return nil, err
2016-03-08 03:37:39 +03:00
}
2017-09-30 16:56:52 +03:00
} else {
json = string(data)
2016-03-08 03:37:39 +03:00
}
2017-09-30 04:11:05 +03:00
config := &Config{
path: path,
_followHost: gjson.Get(json, FollowHost).String(),
_followPort: gjson.Get(json, FollowPort).Int(),
_followID: gjson.Get(json, FollowID).String(),
_followPos: gjson.Get(json, FollowPos).Int(),
_serverID: gjson.Get(json, ServerID).String(),
_readOnly: gjson.Get(json, ReadOnly).Bool(),
_requirePassP: gjson.Get(json, RequirePass).String(),
_leaderAuthP: gjson.Get(json, LeaderAuth).String(),
_protectedModeP: gjson.Get(json, ProtectedMode).String(),
_maxMemoryP: gjson.Get(json, MaxMemory).String(),
_autoGCP: gjson.Get(json, AutoGC).String(),
_keepAliveP: gjson.Get(json, KeepAlive).String(),
2016-03-08 03:37:39 +03:00
}
// load properties
2017-09-30 04:11:05 +03:00
if err := config.setProperty(RequirePass, config._requirePassP, true); err != nil {
return nil, err
}
2017-09-30 04:11:05 +03:00
if err := config.setProperty(LeaderAuth, config._leaderAuthP, true); err != nil {
return nil, err
2016-03-08 03:37:39 +03:00
}
2017-09-30 04:11:05 +03:00
if err := config.setProperty(ProtectedMode, config._protectedModeP, true); err != nil {
return nil, err
2016-03-08 03:37:39 +03:00
}
2017-09-30 04:11:05 +03:00
if err := config.setProperty(MaxMemory, config._maxMemoryP, true); err != nil {
return nil, err
2016-03-08 03:37:39 +03:00
}
2017-09-30 04:11:05 +03:00
if err := config.setProperty(AutoGC, config._autoGCP, true); err != nil {
return nil, err
2016-05-24 05:44:25 +03:00
}
2017-09-30 04:11:05 +03:00
if err := config.setProperty(KeepAlive, config._keepAliveP, true); err != nil {
return nil, err
}
2017-09-30 04:11:05 +03:00
config.write(false)
return config, nil
}
func (config *Config) write(writeProperties bool) {
config.mu.Lock()
defer config.mu.Unlock()
if writeProperties {
// save properties
config._requirePassP = config._requirePass
config._leaderAuthP = config._leaderAuth
if config._protectedMode == defaultProtectedMode {
config._protectedModeP = ""
} else {
config._protectedModeP = config._protectedMode
}
config._maxMemoryP = formatMemSize(config._maxMemory)
if config._autoGC == 0 {
config._autoGCP = ""
} else {
config._autoGCP = strconv.FormatUint(config._autoGC, 10)
}
if config._keepAlive == defaultKeepAlive {
config._keepAliveP = ""
} else {
config._keepAliveP = strconv.FormatUint(uint64(config._keepAlive), 10)
}
}
m := make(map[string]interface{})
if config._followHost != "" {
m[FollowHost] = config._followHost
}
if config._followPort != 0 {
m[FollowPort] = config._followPort
}
if config._followID != "" {
m[FollowID] = config._followID
}
if config._followPos != 0 {
m[FollowPos] = config._followPos
}
if config._serverID != "" {
m[ServerID] = config._serverID
}
if config._readOnly {
m[ReadOnly] = config._readOnly
}
if config._requirePassP != "" {
m[RequirePass] = config._requirePassP
}
if config._leaderAuthP != "" {
m[LeaderAuth] = config._leaderAuthP
}
if config._protectedModeP != "" {
m[ProtectedMode] = config._protectedModeP
}
if config._maxMemoryP != "" {
m[MaxMemory] = config._maxMemoryP
}
if config._autoGCP != "" {
m[AutoGC] = config._autoGCP
}
if config._keepAliveP != "" {
m[KeepAlive] = config._keepAliveP
}
data, err := json.MarshalIndent(m, "", "\t")
if err != nil {
panic(err)
}
data = append(data, '\n')
err = ioutil.WriteFile(config.path, data, 0600)
if err != nil {
panic(err)
}
}
func parseMemSize(s string) (bytes int64, ok bool) {
2016-05-24 05:44:25 +03:00
if s == "" {
return 0, true
}
s = strings.ToLower(s)
var n uint64
2017-09-30 04:11:05 +03:00
var sz int64
2016-05-24 05:44:25 +03:00
var err error
if strings.HasSuffix(s, "gb") {
n, err = strconv.ParseUint(s[:len(s)-2], 10, 64)
2017-09-30 04:11:05 +03:00
sz = int64(n * 1024 * 1024 * 1024)
2016-05-24 05:44:25 +03:00
} else if strings.HasSuffix(s, "mb") {
n, err = strconv.ParseUint(s[:len(s)-2], 10, 64)
2017-09-30 04:11:05 +03:00
sz = int64(n * 1024 * 1024)
2016-05-24 05:44:25 +03:00
} else if strings.HasSuffix(s, "kb") {
n, err = strconv.ParseUint(s[:len(s)-2], 10, 64)
2017-09-30 04:11:05 +03:00
sz = int64(n * 1024)
2016-05-24 05:44:25 +03:00
} else {
n, err = strconv.ParseUint(s, 10, 64)
2017-09-30 04:11:05 +03:00
sz = int64(n)
2016-05-24 05:44:25 +03:00
}
if err != nil {
return 0, false
}
return sz, true
}
2017-09-30 04:11:05 +03:00
func formatMemSize(sz int64) string {
2016-05-24 05:44:25 +03:00
if sz <= 0 {
return ""
}
if sz < 1024 {
2017-09-30 04:11:05 +03:00
return strconv.FormatInt(sz, 10)
2016-05-24 05:44:25 +03:00
}
sz /= 1024
if sz < 1024 {
2017-09-30 04:11:05 +03:00
return strconv.FormatInt(sz, 10) + "kb"
2016-05-24 05:44:25 +03:00
}
sz /= 1024
if sz < 1024 {
2017-09-30 04:11:05 +03:00
return strconv.FormatInt(sz, 10) + "mb"
2016-05-24 05:44:25 +03:00
}
sz /= 1024
2017-09-30 04:11:05 +03:00
return strconv.FormatInt(sz, 10) + "gb"
2016-05-24 05:44:25 +03:00
}
2017-09-30 04:11:05 +03:00
func (config *Config) setProperty(name, value string, fromLoad bool) error {
config.mu.Lock()
defer config.mu.Unlock()
2016-03-08 03:37:39 +03:00
var invalid bool
switch name {
default:
return clientErrorf("Unsupported CONFIG parameter: %s", name)
case RequirePass:
2017-09-30 04:11:05 +03:00
config._requirePass = value
case LeaderAuth:
2017-09-30 04:11:05 +03:00
config._leaderAuth = value
case AutoGC:
if value == "" {
2017-09-30 04:11:05 +03:00
config._autoGC = 0
} else {
gc, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return err
}
2017-09-30 04:11:05 +03:00
config._autoGC = gc
}
case MaxMemory:
2016-05-24 05:44:25 +03:00
sz, ok := parseMemSize(value)
if !ok {
return clientErrorf("Invalid argument '%s' for CONFIG SET '%s'", value, name)
2016-05-24 05:44:25 +03:00
}
2017-09-30 04:11:05 +03:00
config._maxMemory = sz
case ProtectedMode:
2016-03-08 03:37:39 +03:00
switch strings.ToLower(value) {
case "":
if fromLoad {
2017-09-30 04:11:05 +03:00
config._protectedMode = defaultProtectedMode
2016-03-08 03:37:39 +03:00
} else {
invalid = true
}
case "yes", "no":
2017-09-30 04:11:05 +03:00
config._protectedMode = strings.ToLower(value)
2016-03-08 03:37:39 +03:00
default:
invalid = true
}
case KeepAlive:
if value == "" {
2017-09-30 04:11:05 +03:00
config._keepAlive = defaultKeepAlive
} else {
keepalive, err := strconv.ParseUint(value, 10, 64)
if err != nil {
invalid = true
} else {
2017-09-30 04:11:05 +03:00
config._keepAlive = int64(keepalive)
}
}
2016-03-08 03:37:39 +03:00
}
2016-03-08 03:37:39 +03:00
if invalid {
return clientErrorf("Invalid argument '%s' for CONFIG SET '%s'", value, name)
2016-03-08 03:37:39 +03:00
}
return nil
}
2017-09-30 04:11:05 +03:00
func (config *Config) getProperties(pattern string) map[string]interface{} {
2016-03-29 15:53:53 +03:00
m := make(map[string]interface{})
for _, name := range validProperties {
2016-07-12 22:18:16 +03:00
matched, _ := glob.Match(pattern, name)
2016-03-29 15:53:53 +03:00
if matched {
2017-09-30 04:11:05 +03:00
m[name] = config.getProperty(name)
2016-03-29 15:53:53 +03:00
}
}
return m
}
2017-09-30 04:11:05 +03:00
func (config *Config) getProperty(name string) string {
config.mu.RLock()
defer config.mu.RUnlock()
2016-03-08 03:37:39 +03:00
switch name {
default:
return ""
case AutoGC:
2017-09-30 04:11:05 +03:00
return strconv.FormatUint(config._autoGC, 10)
case RequirePass:
2017-09-30 04:11:05 +03:00
return config._requirePass
case LeaderAuth:
2017-09-30 04:11:05 +03:00
return config._leaderAuth
case ProtectedMode:
2017-09-30 04:11:05 +03:00
return config._protectedMode
case MaxMemory:
2017-09-30 04:11:05 +03:00
return formatMemSize(config._maxMemory)
case KeepAlive:
2017-09-30 04:11:05 +03:00
return strconv.FormatUint(uint64(config._keepAlive), 10)
2016-03-08 03:37:39 +03:00
}
}
func (s *Server) cmdConfigGet(msg *Message) (res resp.Value, err error) {
2016-03-29 15:53:53 +03:00
start := time.Now()
vs := msg.Args[1:]
2016-03-29 15:53:53 +03:00
var ok bool
var name string
2017-10-05 18:20:40 +03:00
2016-03-29 15:53:53 +03:00
if vs, name, ok = tokenval(vs); !ok {
return NOMessage, errInvalidNumberOfArguments
2016-03-08 03:37:39 +03:00
}
2016-03-29 15:53:53 +03:00
if len(vs) != 0 {
return NOMessage, errInvalidNumberOfArguments
2016-03-29 15:53:53 +03:00
}
m := s.config.getProperties(name)
2016-03-29 15:53:53 +03:00
switch msg.OutputType {
case JSON:
2016-03-29 15:53:53 +03:00
data, err := json.Marshal(m)
if err != nil {
return NOMessage, err
2016-03-08 03:37:39 +03:00
}
res = resp.StringValue(`{"ok":true,"properties":` + string(data) + `,"elapsed":"` + time.Since(start).String() + "\"}")
case RESP:
2016-03-29 15:53:53 +03:00
vals := respValuesSimpleMap(m)
2017-10-05 18:20:40 +03:00
res = resp.ArrayValue(vals)
2016-03-29 15:53:53 +03:00
}
return
}
func (s *Server) cmdConfigSet(msg *Message) (res resp.Value, err error) {
2016-03-29 15:53:53 +03:00
start := time.Now()
vs := msg.Args[1:]
2016-03-29 15:53:53 +03:00
var ok bool
var name string
2017-10-05 18:20:40 +03:00
2016-03-29 15:53:53 +03:00
if vs, name, ok = tokenval(vs); !ok {
return NOMessage, errInvalidNumberOfArguments
2016-03-29 15:53:53 +03:00
}
var value string
if vs, value, ok = tokenval(vs); !ok {
if strings.ToLower(name) != RequirePass {
return NOMessage, errInvalidNumberOfArguments
}
2016-03-29 15:53:53 +03:00
}
if len(vs) != 0 {
return NOMessage, errInvalidNumberOfArguments
2016-03-29 15:53:53 +03:00
}
if err := s.config.setProperty(name, value, false); err != nil {
return NOMessage, err
2016-03-29 15:53:53 +03:00
}
return OKMessage(msg, start), nil
2016-03-29 15:53:53 +03:00
}
func (s *Server) cmdConfigRewrite(msg *Message) (res resp.Value, err error) {
2016-03-29 15:53:53 +03:00
start := time.Now()
vs := msg.Args[1:]
2017-10-05 18:20:40 +03:00
2016-03-29 15:53:53 +03:00
if len(vs) != 0 {
return NOMessage, errInvalidNumberOfArguments
2016-03-29 15:53:53 +03:00
}
s.config.write(true)
return OKMessage(msg, start), nil
2016-03-08 03:37:39 +03:00
}
2017-09-30 16:56:52 +03:00
func (config *Config) followHost() string {
config.mu.RLock()
v := config._followHost
config.mu.RUnlock()
return v
}
func (config *Config) followPort() int {
config.mu.RLock()
v := config._followPort
config.mu.RUnlock()
return int(v)
}
func (config *Config) serverID() string {
config.mu.RLock()
v := config._serverID
config.mu.RUnlock()
return v
}
func (config *Config) readOnly() bool {
config.mu.RLock()
v := config._readOnly
config.mu.RUnlock()
return v
}
func (config *Config) requirePass() string {
config.mu.RLock()
v := config._requirePass
config.mu.RUnlock()
return v
}
func (config *Config) leaderAuth() string {
config.mu.RLock()
v := config._leaderAuth
config.mu.RUnlock()
return v
}
func (config *Config) protectedMode() string {
config.mu.RLock()
v := config._protectedMode
config.mu.RUnlock()
return v
}
func (config *Config) maxMemory() int {
config.mu.RLock()
v := config._maxMemory
config.mu.RUnlock()
return int(v)
}
func (config *Config) autoGC() uint64 {
config.mu.RLock()
v := config._autoGC
config.mu.RUnlock()
return v
}
func (config *Config) keepAlive() int64 {
config.mu.RLock()
v := config._keepAlive
config.mu.RUnlock()
return v
}
func (config *Config) setFollowHost(v string) {
config.mu.Lock()
config._followHost = v
config.mu.Unlock()
}
func (config *Config) setFollowPort(v int) {
config.mu.Lock()
config._followPort = int64(v)
config.mu.Unlock()
}
func (config *Config) setReadOnly(v bool) {
config.mu.Lock()
config._readOnly = v
config.mu.Unlock()
}