github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/interlock/set_config.go (about) 1 // Copyright 2020 WHTCORPS INC, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package interlock 15 16 import ( 17 "bytes" 18 "context" 19 "fmt" 20 "io/ioutil" 21 "net" 22 "net/http" 23 "strings" 24 25 "github.com/whtcorpsinc/BerolinaSQL/allegrosql" 26 "github.com/whtcorpsinc/errors" 27 "github.com/whtcorpsinc/milevadb/causet/embedded" 28 "github.com/whtcorpsinc/milevadb/memex" 29 "github.com/whtcorpsinc/milevadb/schemareplicant" 30 "github.com/whtcorpsinc/milevadb/soliton" 31 "github.com/whtcorpsinc/milevadb/soliton/FIDelapi" 32 "github.com/whtcorpsinc/milevadb/soliton/chunk" 33 "github.com/whtcorpsinc/milevadb/soliton/set" 34 "github.com/whtcorpsinc/milevadb/soliton/stringutil" 35 "github.com/whtcorpsinc/milevadb/stochastikctx" 36 "github.com/whtcorpsinc/milevadb/types" 37 ) 38 39 // SetConfigInterDirc executes 'SET CONFIG' memex. 40 type SetConfigInterDirc struct { 41 baseInterlockingDirectorate 42 p *embedded.SetConfig 43 jsonBody string 44 } 45 46 // Open implements the InterlockingDirectorate Open interface. 47 func (s *SetConfigInterDirc) Open(ctx context.Context) error { 48 if s.p.Type != "" { 49 s.p.Type = strings.ToLower(s.p.Type) 50 if s.p.Type != "einsteindb" && s.p.Type != "milevadb" && s.p.Type != "fidel" { 51 return errors.Errorf("unknown type %v", s.p.Type) 52 } 53 if s.p.Type == "milevadb" { 54 return errors.Errorf("MilevaDB doesn't support to change configs online, please use ALLEGROALLEGROSQL variables") 55 } 56 } 57 if s.p.Instance != "" { 58 s.p.Instance = strings.ToLower(s.p.Instance) 59 if !isValidInstance(s.p.Instance) { 60 return errors.Errorf("invalid instance %v", s.p.Instance) 61 } 62 } 63 s.p.Name = strings.ToLower(s.p.Name) 64 65 body, err := ConvertConfigItem2JSON(s.ctx, s.p.Name, s.p.Value) 66 s.jsonBody = body 67 return err 68 } 69 70 // TestSetConfigServerInfoKey is used as the key to causetstore 'TestSetConfigServerInfoFunc' in the context. 71 var TestSetConfigServerInfoKey stringutil.StringerStr = "TestSetConfigServerInfoKey" 72 73 // TestSetConfigHTTPHandlerKey is used as the key to causetstore 'TestSetConfigDoRequestFunc' in the context. 74 var TestSetConfigHTTPHandlerKey stringutil.StringerStr = "TestSetConfigHTTPHandlerKey" 75 76 // Next implements the InterlockingDirectorate Next interface. 77 func (s *SetConfigInterDirc) Next(ctx context.Context, req *chunk.Chunk) error { 78 req.Reset() 79 getServerFunc := schemareplicant.GetClusterServerInfo 80 if v := s.ctx.Value(TestSetConfigServerInfoKey); v != nil { 81 getServerFunc = v.(func(stochastikctx.Context) ([]schemareplicant.ServerInfo, error)) 82 } 83 84 serversInfo, err := getServerFunc(s.ctx) 85 if err != nil { 86 return err 87 } 88 nodeTypes := set.NewStringSet() 89 nodeAddrs := set.NewStringSet() 90 if s.p.Type != "" { 91 nodeTypes.Insert(s.p.Type) 92 } 93 if s.p.Instance != "" { 94 nodeAddrs.Insert(s.p.Instance) 95 } 96 serversInfo = filterClusterServerInfo(serversInfo, nodeTypes, nodeAddrs) 97 if s.p.Instance != "" && len(serversInfo) == 0 { 98 return errors.Errorf("instance %v is not found in this cluster", s.p.Instance) 99 } 100 101 for _, serverInfo := range serversInfo { 102 var url string 103 switch serverInfo.ServerType { 104 case "fidel": 105 url = fmt.Sprintf("%s://%s%s", soliton.InternalHTTPSchema(), serverInfo.StatusAddr, FIDelapi.Config) 106 case "einsteindb": 107 url = fmt.Sprintf("%s://%s/config", soliton.InternalHTTPSchema(), serverInfo.StatusAddr) 108 case "milevadb": 109 return errors.Errorf("MilevaDB doesn't support to change configs online, please use ALLEGROALLEGROSQL variables") 110 default: 111 return errors.Errorf("Unknown server type %s", serverInfo.ServerType) 112 } 113 if err := s.doRequest(url); err != nil { 114 s.ctx.GetStochastikVars().StmtCtx.AppendWarning(err) 115 } 116 } 117 return nil 118 } 119 120 func (s *SetConfigInterDirc) doRequest(url string) (retErr error) { 121 body := bytes.NewBufferString(s.jsonBody) 122 req, err := http.NewRequest(http.MethodPost, url, body) 123 if err != nil { 124 return err 125 } 126 var httpHandler func(req *http.Request) (*http.Response, error) 127 if v := s.ctx.Value(TestSetConfigHTTPHandlerKey); v != nil { 128 httpHandler = v.(func(*http.Request) (*http.Response, error)) 129 } else { 130 httpHandler = soliton.InternalHTTPClient().Do 131 } 132 resp, err := httpHandler(req) 133 if err != nil { 134 return err 135 } 136 defer func() { 137 if err := resp.Body.Close(); err != nil { 138 if retErr == nil { 139 retErr = err 140 } 141 } 142 }() 143 if resp.StatusCode == http.StatusOK { 144 return nil 145 } else if resp.StatusCode >= 400 && resp.StatusCode < 600 { 146 message, err := ioutil.ReadAll(resp.Body) 147 if err != nil { 148 return err 149 } 150 return errors.Errorf("bad request to %s: %s", url, message) 151 } 152 return errors.Errorf("request %s failed: %s", url, resp.Status) 153 } 154 155 func isValidInstance(instance string) bool { 156 ip, port, err := net.SplitHostPort(instance) 157 if err != nil { 158 return false 159 } 160 if port == "" { 161 return false 162 } 163 v := net.ParseIP(ip) 164 return v != nil 165 } 166 167 // ConvertConfigItem2JSON converts the config item specified by key and val to json. 168 // For example: 169 // set config x key="val" ==> {"key":"val"} 170 // set config x key=233 ==> {"key":233} 171 func ConvertConfigItem2JSON(ctx stochastikctx.Context, key string, val memex.Expression) (body string, err error) { 172 if val == nil { 173 return "", errors.Errorf("cannot set config to null") 174 } 175 isNull := false 176 str := "" 177 switch val.GetType().EvalType() { 178 case types.ETString: 179 var s string 180 s, isNull, err = val.EvalString(ctx, chunk.Event{}) 181 if err == nil && !isNull { 182 str = fmt.Sprintf(`"%s"`, s) 183 } 184 case types.ETInt: 185 var i int64 186 i, isNull, err = val.EvalInt(ctx, chunk.Event{}) 187 if err == nil && !isNull { 188 if allegrosql.HasIsBooleanFlag(val.GetType().Flag) { 189 str = "true" 190 if i == 0 { 191 str = "false" 192 } 193 } else { 194 str = fmt.Sprintf("%v", i) 195 } 196 } 197 case types.ETReal: 198 var f float64 199 f, isNull, err = val.EvalReal(ctx, chunk.Event{}) 200 if err == nil && !isNull { 201 str = fmt.Sprintf("%v", f) 202 } 203 case types.ETDecimal: 204 var d *types.MyDecimal 205 d, isNull, err = val.EvalDecimal(ctx, chunk.Event{}) 206 if err == nil && !isNull { 207 str = string(d.ToString()) 208 } 209 default: 210 return "", errors.Errorf("unsupported config value type") 211 } 212 if err != nil { 213 return 214 } 215 if isNull { 216 return "", errors.Errorf("can't set config to null") 217 } 218 body = fmt.Sprintf(`{"%s":%s}`, key, str) 219 return body, nil 220 }