Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: support input with a prefix when using config #2354

Merged
merged 6 commits into from
Apr 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/apiutil/serverapi/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func IsServiceAllowed(s *server.Server, group server.ServiceGroup) bool {
}

opt := s.GetServerOption()
cfg := opt.LoadPDServerConfig()
cfg := opt.GetPDServerConfig()
if cfg != nil {
for _, allow := range cfg.RuntimeServices {
if group.Name == allow {
Expand Down
2 changes: 1 addition & 1 deletion server/api/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (s *testComponentSuite) TestComponent(c *C) {
c.Assert(strings.Contains(err.Error(), "404"), IsTrue)
c.Assert(len(output1), Equals, 0)

// register 2 c1 and 1 c2
// register 2 c1, 1 c2, and 1 c3
reqs := []map[string]string{
{"component": "c1", "addr": "127.0.0.1:1"},
{"component": "c1", "addr": "127.0.0.1:2"},
Expand Down
197 changes: 171 additions & 26 deletions server/api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ package api
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"strings"

"github.com/pingcap/errcode"
"github.com/pingcap/log"
"github.com/pingcap/pd/v4/pkg/apiutil"
"github.com/pingcap/pd/v4/pkg/logutil"
"github.com/pingcap/pd/v4/server"
"github.com/pingcap/pd/v4/server/config"
"github.com/pkg/errors"
Expand Down Expand Up @@ -75,69 +78,211 @@ func (h *confHandler) GetDefault(w http.ResponseWriter, r *http.Request) {
// @Success 200 {string} string "The config is updated."
// @Failure 400 {string} string "The input is invalid."
// @Failure 500 {string} string "PD server failed to proceed the request."
// @Failure 503 {string} string "PD server has no leader."
// @Router /config [post]
func (h *confHandler) Post(w http.ResponseWriter, r *http.Request) {
config := h.svr.GetConfig()
cfg := h.svr.GetConfig()
data, err := ioutil.ReadAll(r.Body)
r.Body.Close()
if err != nil {
h.rd.JSON(w, http.StatusInternalServerError, err.Error())
return
}
found1, err := h.updateSchedule(data, config)
if err != nil {
h.rd.JSON(w, http.StatusInternalServerError, err.Error())

conf := make(map[string]interface{})
if err := json.Unmarshal(data, &conf); err != nil {
h.rd.JSON(w, http.StatusBadRequest, err.Error())
return
}
found2, err := h.updateReplication(data, config)
if err != nil {
h.rd.JSON(w, http.StatusInternalServerError, err.Error())
return

for k, v := range conf {
if s := strings.Split(k, "."); len(s) > 1 {
if err := h.updateConfig(cfg, k, v); err != nil {
h.rd.JSON(w, http.StatusBadRequest, err.Error())
return
}
continue
}
key := findTag(reflect.TypeOf(config.Config{}), k)
if key == "" {
h.rd.JSON(w, http.StatusBadRequest, fmt.Sprintf("config item %s not found", k))
return
}
if err := h.updateConfig(cfg, key, v); err != nil {
h.rd.JSON(w, http.StatusBadRequest, err.Error())
return
}
}
found3, err := h.updatePDServerConfig(data, config)
if err != nil {
h.rd.JSON(w, http.StatusInternalServerError, err.Error())
return

h.rd.JSON(w, http.StatusOK, nil)
}

func (h *confHandler) updateConfig(cfg *config.Config, key string, value interface{}) error {
kp := strings.Split(key, ".")
switch kp[0] {
case "schedule":
rleungx marked this conversation as resolved.
Show resolved Hide resolved
return h.updateSchedule(cfg, kp[len(kp)-1], value)
case "replication":
return h.updateReplication(cfg, kp[len(kp)-1], value)
case "replication-mode":
if len(kp) < 2 {
return errors.Errorf("cannot update config prefix %s", kp[0])
}
return h.updateReplicationModeConfig(cfg, kp[1:], value)
rleungx marked this conversation as resolved.
Show resolved Hide resolved
case "pd-server":
return h.updatePDServerConfig(cfg, kp[len(kp)-1], value)
case "log":
return h.updateLogLevel(kp, value)
case "cluster-version":
return h.updateClusterVersion(value)
case "label-property": // TODO: support changing label-property
}
if !found1 && !found2 && !found3 {
h.rd.JSON(w, http.StatusBadRequest, "config item not found")
return
return errors.Errorf("config prefix %s not found", kp[0])
}

// If we have both "a.c" and "b.c" config items, for a given c, it's hard for us to decide which config item it represents.
// We'd better to naming a config item without duplication.
func findTag(t reflect.Type, tag string) string {
rleungx marked this conversation as resolved.
Show resolved Hide resolved
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)

column := field.Tag.Get("json")
c := strings.Split(column, ",")
if c[0] == tag {
return c[0]
}

if field.Type.Kind() == reflect.Struct {
path := findTag(field.Type, tag)
if path == "" {
continue
}
return field.Tag.Get("json") + "." + path
}
}
h.rd.JSON(w, http.StatusOK, nil)
return ""
}

func (h *confHandler) updateSchedule(data []byte, config *config.Config) (bool, error) {
func (h *confHandler) updateSchedule(config *config.Config, key string, value interface{}) error {
data, err := json.Marshal(map[string]interface{}{key: value})
if err != nil {
return err
}

updated, found, err := h.mergeConfig(&config.Schedule, data)
if err != nil {
return false, err
return err
}

if !found {
return errors.Errorf("config item %s not found", key)
}

if updated {
err = h.svr.SetScheduleConfig(config.Schedule)
}
return found, err
return err
}

func (h *confHandler) updateReplication(data []byte, config *config.Config) (bool, error) {
func (h *confHandler) updateReplication(config *config.Config, key string, value interface{}) error {
data, err := json.Marshal(map[string]interface{}{key: value})
if err != nil {
return err
}

updated, found, err := h.mergeConfig(&config.Replication, data)
if err != nil {
return false, err
return err
}

if !found {
return errors.Errorf("config item %s not found", key)
}

if updated {
err = h.svr.SetReplicationConfig(config.Replication)
}
return found, err
return err
}

func (h *confHandler) updateReplicationModeConfig(config *config.Config, key []string, value interface{}) error {
cfg := make(map[string]interface{})
cfg = getConfigMap(cfg, key, value)
data, err := json.Marshal(cfg)
if err != nil {
return err
}

updated, found, err := h.mergeConfig(&config.ReplicationMode, data)
if err != nil {
return err
}

if !found {
return errors.Errorf("config item %s not found", key)
}

if updated {
err = h.svr.SetReplicationModeConfig(config.ReplicationMode)
}
return err
}

func (h *confHandler) updatePDServerConfig(data []byte, config *config.Config) (bool, error) {
func (h *confHandler) updatePDServerConfig(config *config.Config, key string, value interface{}) error {
data, err := json.Marshal(map[string]interface{}{key: value})
if err != nil {
return err
}

updated, found, err := h.mergeConfig(&config.PDServerCfg, data)
if err != nil {
return false, err
return err
}

if !found {
return errors.Errorf("config item %s not found", key)
}

if updated {
err = h.svr.SetPDServerConfig(config.PDServerCfg)
}
return found, err
return err
}

func (h *confHandler) updateLogLevel(kp []string, value interface{}) error {
if len(kp) != 2 || kp[1] != "level" {
return errors.Errorf("only support changing log level")
}
if level, ok := value.(string); ok {
err := h.svr.SetLogLevel(level)
if err != nil {
return err
}
log.SetLevel(logutil.StringToZapLogLevel(level))
return nil
}
return errors.Errorf("input value %v is illegal", value)
}

func (h *confHandler) updateClusterVersion(value interface{}) error {
if version, ok := value.(string); ok {
err := h.svr.SetClusterVersion(version)
if err != nil {
return err
}
return nil
}
return errors.Errorf("input value %v is illegal", value)
}

func getConfigMap(cfg map[string]interface{}, key []string, value interface{}) map[string]interface{} {
if len(key) == 1 {
cfg[key[0]] = value
return cfg
}

subConfig := make(map[string]interface{})
cfg[key[0]] = getConfigMap(subConfig, key[1:], value)
return cfg
}

func (h *confHandler) mergeConfig(v interface{}, data []byte) (updated bool, found bool, err error) {
Expand Down
63 changes: 63 additions & 0 deletions server/api/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ package api
import (
"encoding/json"
"fmt"
"strings"
"time"

. "github.com/pingcap/check"
"github.com/pingcap/pd/v4/pkg/typeutil"
"github.com/pingcap/pd/v4/server"
"github.com/pingcap/pd/v4/server/cluster"
"github.com/pingcap/pd/v4/server/config"
)

Expand Down Expand Up @@ -50,6 +52,7 @@ func (s *testConfigSuite) TestConfigAll(c *C) {
err := readJSON(addr, cfg)
c.Assert(err, IsNil)

// the original way
r := map[string]int{"max-replicas": 5}
postData, err := json.Marshal(r)
c.Assert(err, IsNil)
Expand Down Expand Up @@ -80,6 +83,66 @@ func (s *testConfigSuite) TestConfigAll(c *C) {
cfg.Schedule.RegionScheduleLimit = 10
cfg.PDServerCfg.MetricStorage = "http://127.0.0.1:9090"
c.Assert(cfg, DeepEquals, newCfg)

// the new way
l = map[string]interface{}{
"schedule.tolerant-size-ratio": 2.5,
"replication.location-labels": "idc,host",
"pd-server.metric-storage": "http://127.0.0.1:1234",
"log.level": "warn",
"cluster-version": "v4.0.0-beta",
"replication-mode.replication-mode": "dr_auto_sync",
"replication-mode.dr-auto-sync.label-key": "foobar",
}
postData, err = json.Marshal(l)
c.Assert(err, IsNil)
err = postJSON(addr, postData)
c.Assert(err, IsNil)
newCfg1 := &config.Config{}
err = readJSON(addr, newCfg1)
c.Assert(err, IsNil)
cfg.Schedule.TolerantSizeRatio = 2.5
cfg.Replication.LocationLabels = []string{"idc", "host"}
cfg.PDServerCfg.MetricStorage = "http://127.0.0.1:1234"
cfg.Log.Level = "warn"
cfg.ReplicationMode.DRAutoSync.LabelKey = "foobar"
cfg.ReplicationMode.ReplicationMode = "dr_auto_sync"
v, err := cluster.ParseVersion("v4.0.0-beta")
c.Assert(err, IsNil)
cfg.ClusterVersion = *v
c.Assert(newCfg1, DeepEquals, cfg)

postData, err = json.Marshal(l)
c.Assert(err, IsNil)
err = postJSON(addr, postData)
c.Assert(err, IsNil)

// illegal prefix
l = map[string]interface{}{
"replicate.max-replicas": 1,
}
postData, err = json.Marshal(l)
c.Assert(err, IsNil)
err = postJSON(addr, postData)
c.Assert(strings.Contains(err.Error(), "not found"), IsTrue)

// update prefix directly
l = map[string]interface{}{
"replication-mode": nil,
}
postData, err = json.Marshal(l)
c.Assert(err, IsNil)
err = postJSON(addr, postData)
c.Assert(strings.Contains(err.Error(), "cannot update config prefix"), IsTrue)

// config item not found
l = map[string]interface{}{
"schedule.region-limit": 10,
}
postData, err = json.Marshal(l)
c.Assert(err, IsNil)
err = postJSON(addr, postData)
c.Assert(strings.Contains(err.Error(), "not found"), IsTrue)
}

func (s *testConfigSuite) TestConfigSchedule(c *C) {
Expand Down
6 changes: 5 additions & 1 deletion server/api/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ func (h *logHandler) Handle(w http.ResponseWriter, r *http.Request) {
return
}

h.svr.SetLogLevel(level)
err = h.svr.SetLogLevel(level)
if err != nil {
h.rd.JSON(w, http.StatusBadRequest, err.Error())
return
}
log.SetLevel(logutil.StringToZapLogLevel(level))

h.rd.JSON(w, http.StatusOK, nil)
Expand Down
4 changes: 2 additions & 2 deletions server/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (s *testConfigSuite) TestReloadConfig(c *C) {
scheduleCfg := opt.Load()
scheduleCfg.MaxSnapshotCount = 10
opt.SetMaxReplicas(5)
opt.LoadPDServerConfig().UseRegionStorage = true
opt.GetPDServerConfig().UseRegionStorage = true
c.Assert(opt.Persist(storage), IsNil)

// suppose we add a new default enable scheduler "adjacent-region"
Expand All @@ -70,7 +70,7 @@ func (s *testConfigSuite) TestReloadConfig(c *C) {
c.Assert(newOpt.Reload(storage), IsNil)
schedulers := newOpt.GetSchedulers()
c.Assert(schedulers, HasLen, 5)
c.Assert(newOpt.LoadPDServerConfig().UseRegionStorage, IsTrue)
c.Assert(newOpt.GetPDServerConfig().UseRegionStorage, IsTrue)
for i, s := range schedulers {
c.Assert(s.Type, Equals, defaultSchedulers[i])
c.Assert(s.Disable, IsFalse)
Expand Down
Loading