You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
221 lines
6.2 KiB
221 lines
6.2 KiB
// Copyright 2020 PingCAP, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package executor
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/pingcap/errors"
|
|
"github.com/pingcap/parser/mysql"
|
|
"github.com/pingcap/tidb/expression"
|
|
"github.com/pingcap/tidb/infoschema"
|
|
"github.com/pingcap/tidb/planner/core"
|
|
"github.com/pingcap/tidb/sessionctx"
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/pingcap/tidb/util"
|
|
"github.com/pingcap/tidb/util/chunk"
|
|
"github.com/pingcap/tidb/util/pdapi"
|
|
"github.com/pingcap/tidb/util/set"
|
|
"github.com/pingcap/tidb/util/stringutil"
|
|
)
|
|
|
|
// SetConfigExec executes 'SET CONFIG' statement.
|
|
type SetConfigExec struct {
|
|
baseExecutor
|
|
p *core.SetConfig
|
|
jsonBody string
|
|
}
|
|
|
|
// Open implements the Executor Open interface.
|
|
func (s *SetConfigExec) Open(ctx context.Context) error {
|
|
if s.p.Type != "" {
|
|
s.p.Type = strings.ToLower(s.p.Type)
|
|
if s.p.Type != "tikv" && s.p.Type != "tidb" && s.p.Type != "pd" {
|
|
return errors.Errorf("unknown type %v", s.p.Type)
|
|
}
|
|
if s.p.Type == "tidb" {
|
|
return errors.Errorf("TiDB doesn't support to change configs online, please use SQL variables")
|
|
}
|
|
}
|
|
if s.p.Instance != "" {
|
|
s.p.Instance = strings.ToLower(s.p.Instance)
|
|
if !isValidInstance(s.p.Instance) {
|
|
return errors.Errorf("invalid instance %v", s.p.Instance)
|
|
}
|
|
}
|
|
s.p.Name = strings.ToLower(s.p.Name)
|
|
|
|
body, err := ConvertConfigItem2JSON(s.ctx, s.p.Name, s.p.Value)
|
|
s.jsonBody = body
|
|
return err
|
|
}
|
|
|
|
// TestSetConfigServerInfoKey is used as the key to store 'TestSetConfigServerInfoFunc' in the context.
|
|
var TestSetConfigServerInfoKey stringutil.StringerStr = "TestSetConfigServerInfoKey"
|
|
|
|
// TestSetConfigHTTPHandlerKey is used as the key to store 'TestSetConfigDoRequestFunc' in the context.
|
|
var TestSetConfigHTTPHandlerKey stringutil.StringerStr = "TestSetConfigHTTPHandlerKey"
|
|
|
|
// Next implements the Executor Next interface.
|
|
func (s *SetConfigExec) Next(ctx context.Context, req *chunk.Chunk) error {
|
|
req.Reset()
|
|
getServerFunc := infoschema.GetClusterServerInfo
|
|
if v := s.ctx.Value(TestSetConfigServerInfoKey); v != nil {
|
|
getServerFunc = v.(func(sessionctx.Context) ([]infoschema.ServerInfo, error))
|
|
}
|
|
|
|
serversInfo, err := getServerFunc(s.ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nodeTypes := set.NewStringSet()
|
|
nodeAddrs := set.NewStringSet()
|
|
if s.p.Type != "" {
|
|
nodeTypes.Insert(s.p.Type)
|
|
}
|
|
if s.p.Instance != "" {
|
|
nodeAddrs.Insert(s.p.Instance)
|
|
}
|
|
serversInfo = filterClusterServerInfo(serversInfo, nodeTypes, nodeAddrs)
|
|
if s.p.Instance != "" && len(serversInfo) == 0 {
|
|
return errors.Errorf("instance %v is not found in this cluster", s.p.Instance)
|
|
}
|
|
|
|
for _, serverInfo := range serversInfo {
|
|
var url string
|
|
switch serverInfo.ServerType {
|
|
case "pd":
|
|
url = fmt.Sprintf("%s://%s%s", util.InternalHTTPSchema(), serverInfo.StatusAddr, pdapi.Config)
|
|
case "tikv":
|
|
url = fmt.Sprintf("%s://%s/config", util.InternalHTTPSchema(), serverInfo.StatusAddr)
|
|
case "tidb":
|
|
return errors.Errorf("TiDB doesn't support to change configs online, please use SQL variables")
|
|
default:
|
|
return errors.Errorf("Unknown server type %s", serverInfo.ServerType)
|
|
}
|
|
if err := s.doRequest(url); err != nil {
|
|
s.ctx.GetSessionVars().StmtCtx.AppendWarning(err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *SetConfigExec) doRequest(url string) (retErr error) {
|
|
body := bytes.NewBufferString(s.jsonBody)
|
|
req, err := http.NewRequest(http.MethodPost, url, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var httpHandler func(req *http.Request) (*http.Response, error)
|
|
if v := s.ctx.Value(TestSetConfigHTTPHandlerKey); v != nil {
|
|
httpHandler = v.(func(*http.Request) (*http.Response, error))
|
|
} else {
|
|
httpHandler = util.InternalHTTPClient().Do
|
|
}
|
|
resp, err := httpHandler(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := resp.Body.Close(); err != nil {
|
|
if retErr == nil {
|
|
retErr = err
|
|
}
|
|
}
|
|
}()
|
|
if resp.StatusCode == http.StatusOK {
|
|
return nil
|
|
} else if resp.StatusCode >= 400 && resp.StatusCode < 600 {
|
|
message, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return errors.Errorf("bad request to %s: %s", url, message)
|
|
}
|
|
return errors.Errorf("request %s failed: %s", url, resp.Status)
|
|
}
|
|
|
|
func isValidInstance(instance string) bool {
|
|
ip, port, err := net.SplitHostPort(instance)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if port == "" {
|
|
return false
|
|
}
|
|
v := net.ParseIP(ip)
|
|
return v != nil
|
|
}
|
|
|
|
// ConvertConfigItem2JSON converts the config item specified by key and val to json.
|
|
// For example:
|
|
//
|
|
// set config x key="val" ==> {"key":"val"}
|
|
// set config x key=233 ==> {"key":233}
|
|
func ConvertConfigItem2JSON(ctx sessionctx.Context, key string, val expression.Expression) (body string, err error) {
|
|
if val == nil {
|
|
return "", errors.Errorf("cannot set config to null")
|
|
}
|
|
isNull := false
|
|
str := ""
|
|
switch val.GetType().EvalType() {
|
|
case types.ETString:
|
|
var s string
|
|
s, isNull, err = val.EvalString(ctx, chunk.Row{})
|
|
if err == nil && !isNull {
|
|
str = fmt.Sprintf(`"%s"`, s)
|
|
}
|
|
case types.ETInt:
|
|
var i int64
|
|
i, isNull, err = val.EvalInt(ctx, chunk.Row{})
|
|
if err == nil && !isNull {
|
|
if mysql.HasIsBooleanFlag(val.GetType().Flag) {
|
|
str = "true"
|
|
if i == 0 {
|
|
str = "false"
|
|
}
|
|
} else {
|
|
str = fmt.Sprintf("%v", i)
|
|
}
|
|
}
|
|
case types.ETReal:
|
|
var f float64
|
|
f, isNull, err = val.EvalReal(ctx, chunk.Row{})
|
|
if err == nil && !isNull {
|
|
str = fmt.Sprintf("%v", f)
|
|
}
|
|
case types.ETDecimal:
|
|
d := new(types.MyDecimal)
|
|
d, isNull, err = val.EvalDecimal(ctx, chunk.Row{})
|
|
if err == nil && !isNull {
|
|
str = string(d.ToString())
|
|
}
|
|
default:
|
|
return "", errors.Errorf("unsupported config value type")
|
|
}
|
|
if err != nil {
|
|
return
|
|
}
|
|
if isNull {
|
|
return "", errors.Errorf("can't set config to null")
|
|
}
|
|
body = fmt.Sprintf(`{"%s":%s}`, key, str)
|
|
return body, nil
|
|
}
|
|
|