github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/allegrosql/server/http_status.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 server 15 16 import ( 17 "archive/zip" 18 "bytes" 19 "context" 20 "crypto/tls" 21 "crypto/x509" 22 "encoding/json" 23 "fmt" 24 "net" 25 "net/http" 26 "net/http/pprof" 27 "net/url" 28 "runtime" 29 rpprof "runtime/pprof" 30 "strconv" 31 "strings" 32 "time" 33 34 "github.com/gorilla/mux" 35 "github.com/prometheus/client_golang/prometheus/promhttp" 36 "github.com/soheilhy/cmux" 37 "github.com/tiancaiamao/apFIDelash/traceapp" 38 "github.com/whtcorpsinc/BerolinaSQL/allegrosql" 39 "github.com/whtcorpsinc/BerolinaSQL/terror" 40 "github.com/whtcorpsinc/errors" 41 "github.com/whtcorpsinc/failpoint" 42 "github.com/whtcorpsinc/fn" 43 "github.com/whtcorpsinc/milevadb/config" 44 "github.com/whtcorpsinc/milevadb/ekv" 45 "github.com/whtcorpsinc/milevadb/soliton" 46 "github.com/whtcorpsinc/milevadb/soliton/logutil" 47 "github.com/whtcorpsinc/milevadb/soliton/printer" 48 "github.com/whtcorpsinc/milevadb/soliton/versioninfo" 49 "go.uber.org/zap" 50 "google.golang.org/grpc/channelz/service" 51 static "sourcegraph.com/sourcegraph/apFIDelash-data" 52 ) 53 54 const defaultStatusPort = 10080 55 56 func (s *Server) startStatusHTTP() { 57 go s.startHTTPServer() 58 } 59 60 func serveError(w http.ResponseWriter, status int, txt string) { 61 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 62 w.Header().Set("X-Go-Pprof", "1") 63 w.Header().Del("Content-Disposition") 64 w.WriteHeader(status) 65 _, err := fmt.Fprintln(w, txt) 66 terror.Log(err) 67 } 68 69 func sleepWithCtx(ctx context.Context, d time.Duration) { 70 select { 71 case <-time.After(d): 72 case <-ctx.Done(): 73 } 74 } 75 76 func (s *Server) listenStatusHTTPServer() error { 77 s.statusAddr = fmt.Sprintf("%s:%d", s.cfg.Status.StatusHost, s.cfg.Status.StatusPort) 78 if s.cfg.Status.StatusPort == 0 && !runInGoTest { 79 s.statusAddr = fmt.Sprintf("%s:%d", s.cfg.Status.StatusHost, defaultStatusPort) 80 } 81 82 logutil.BgLogger().Info("for status and metrics report", zap.String("listening on addr", s.statusAddr)) 83 tlsConfig, err := s.cfg.Security.ToTLSConfig() 84 if err != nil { 85 logutil.BgLogger().Error("invalid TLS config", zap.Error(err)) 86 return errors.Trace(err) 87 } 88 tlsConfig = s.setCNChecker(tlsConfig) 89 90 if tlsConfig != nil { 91 // we need to manage TLS here for cmux to distinguish between HTTP and gRPC. 92 s.statusListener, err = tls.Listen("tcp", s.statusAddr, tlsConfig) 93 } else { 94 s.statusListener, err = net.Listen("tcp", s.statusAddr) 95 } 96 if err != nil { 97 logutil.BgLogger().Info("listen failed", zap.Error(err)) 98 return errors.Trace(err) 99 } else if runInGoTest && s.cfg.Status.StatusPort == 0 { 100 s.statusAddr = s.statusListener.Addr().String() 101 s.cfg.Status.StatusPort = uint(s.statusListener.Addr().(*net.TCPAddr).Port) 102 } 103 return nil 104 } 105 106 func (s *Server) startHTTPServer() { 107 router := mux.NewRouter() 108 109 router.HandleFunc("/status", s.handleStatus).Name("Status") 110 // HTTP path for prometheus. 111 router.Handle("/metrics", promhttp.Handler()).Name("Metrics") 112 113 // HTTP path for dump statistics. 114 router.Handle("/stats/dump/{EDB}/{causet}", s.newStatsHandler()).Name("StatsDump") 115 router.Handle("/stats/dump/{EDB}/{causet}/{snapshot}", s.newStatsHistoryHandler()).Name("StatsHistoryDump") 116 117 router.Handle("/settings", settingsHandler{}).Name("Settings") 118 router.Handle("/binlog/recover", binlogRecover{}).Name("BinlogRecover") 119 120 einsteindbHandlerTool := s.newEinsteinDBHandlerTool() 121 router.Handle("/schemaReplicant", schemaHandler{einsteindbHandlerTool}).Name("Schema") 122 router.Handle("/schemaReplicant/{EDB}", schemaHandler{einsteindbHandlerTool}) 123 router.Handle("/schemaReplicant/{EDB}/{causet}", schemaHandler{einsteindbHandlerTool}) 124 router.Handle("/blocks/{defCausID}/{defCausTp}/{defCausFlag}/{defCausLen}", valueHandler{}) 125 router.Handle("/dbs/history", dbsHistoryJobHandler{einsteindbHandlerTool}).Name("DBS_History") 126 router.Handle("/dbs/tenant/resign", dbsResignTenantHandler{einsteindbHandlerTool.CausetStore.(ekv.CausetStorage)}).Name("DBS_Tenant_Resign") 127 128 // HTTP path for get the MilevaDB config 129 router.Handle("/config", fn.Wrap(func() (*config.Config, error) { 130 return config.GetGlobalConfig(), nil 131 })) 132 133 // HTTP path for get server info. 134 router.Handle("/info", serverInfoHandler{einsteindbHandlerTool}).Name("Info") 135 router.Handle("/info/all", allServerInfoHandler{einsteindbHandlerTool}).Name("InfoALL") 136 // HTTP path for get EDB and causet info that is related to the blockID. 137 router.Handle("/EDB-causet/{blockID}", dbBlockHandler{einsteindbHandlerTool}) 138 // HTTP path for get causet tiflash replica info. 139 router.Handle("/tiflash/replica", flashReplicaHandler{einsteindbHandlerTool}) 140 141 if s.cfg.CausetStore == "einsteindb" { 142 // HTTP path for einsteindb. 143 router.Handle("/blocks/{EDB}/{causet}/regions", blockHandler{einsteindbHandlerTool, opBlockRegions}) 144 router.Handle("/blocks/{EDB}/{causet}/scatter", blockHandler{einsteindbHandlerTool, opBlockScatter}) 145 router.Handle("/blocks/{EDB}/{causet}/stop-scatter", blockHandler{einsteindbHandlerTool, opStopBlockScatter}) 146 router.Handle("/blocks/{EDB}/{causet}/disk-usage", blockHandler{einsteindbHandlerTool, opBlockDiskUsage}) 147 router.Handle("/regions/spacetime", regionHandler{einsteindbHandlerTool}).Name("RegionsMeta") 148 router.Handle("/regions/hot", regionHandler{einsteindbHandlerTool}).Name("RegionHot") 149 router.Handle("/regions/{regionID}", regionHandler{einsteindbHandlerTool}) 150 } 151 152 // HTTP path for get MVCC info 153 router.Handle("/mvcc/key/{EDB}/{causet}/{handle}", mvccTxnHandler{einsteindbHandlerTool, opMvccGetByKey}) 154 router.Handle("/mvcc/txn/{startTS}/{EDB}/{causet}", mvccTxnHandler{einsteindbHandlerTool, opMvccGetByTxn}) 155 router.Handle("/mvcc/hex/{hexKey}", mvccTxnHandler{einsteindbHandlerTool, opMvccGetByHex}) 156 router.Handle("/mvcc/index/{EDB}/{causet}/{index}/{handle}", mvccTxnHandler{einsteindbHandlerTool, opMvccGetByIdx}) 157 158 // HTTP path for generate metric profile. 159 router.Handle("/metrics/profile", profileHandler{einsteindbHandlerTool}) 160 // HTTP path for web UI. 161 if host, port, err := net.SplitHostPort(s.statusAddr); err == nil { 162 if host == "" { 163 host = "localhost" 164 } 165 baseURL := &url.URL{ 166 Scheme: soliton.InternalHTTPSchema(), 167 Host: fmt.Sprintf("%s:%s", host, port), 168 } 169 router.HandleFunc("/web/trace", traceapp.HandleMilevaDB).Name("Trace Viewer") 170 sr := router.PathPrefix("/web/trace/").Subrouter() 171 if _, err := traceapp.New(traceapp.NewRouter(sr), baseURL); err != nil { 172 logutil.BgLogger().Error("new failed", zap.Error(err)) 173 } 174 router.PathPrefix("/static/").Handler(http.StripPrefix("/static", http.FileServer(static.Data))) 175 } 176 177 serverMux := http.NewServeMux() 178 serverMux.Handle("/", router) 179 180 serverMux.HandleFunc("/debug/pprof/", pprof.Index) 181 serverMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 182 serverMux.HandleFunc("/debug/pprof/profile", pprof.Profile) 183 serverMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 184 serverMux.HandleFunc("/debug/pprof/trace", pprof.Trace) 185 186 serverMux.HandleFunc("/debug/zip", func(w http.ResponseWriter, r *http.Request) { 187 w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="milevadb_debug"`+time.Now().Format("20060102150405")+".zip")) 188 189 // dump goroutine/heap/mutex 190 items := []struct { 191 name string 192 gc int 193 debug int 194 second int 195 }{ 196 {name: "goroutine", debug: 2}, 197 {name: "heap", gc: 1}, 198 {name: "mutex"}, 199 } 200 zw := zip.NewWriter(w) 201 for _, item := range items { 202 p := rpprof.Lookup(item.name) 203 if p == nil { 204 serveError(w, http.StatusNotFound, "Unknown profile") 205 return 206 } 207 if item.gc > 0 { 208 runtime.GC() 209 } 210 fw, err := zw.Create(item.name) 211 if err != nil { 212 serveError(w, http.StatusInternalServerError, fmt.Sprintf("Create zipped %s fail: %v", item.name, err)) 213 return 214 } 215 err = p.WriteTo(fw, item.debug) 216 terror.Log(err) 217 } 218 219 // dump profile 220 fw, err := zw.Create("profile") 221 if err != nil { 222 serveError(w, http.StatusInternalServerError, fmt.Sprintf("Create zipped %s fail: %v", "profile", err)) 223 return 224 } 225 if err := rpprof.StartCPUProfile(fw); err != nil { 226 serveError(w, http.StatusInternalServerError, 227 fmt.Sprintf("Could not enable CPU profiling: %s", err)) 228 return 229 } 230 sec, err := strconv.ParseInt(r.FormValue("seconds"), 10, 64) 231 if sec <= 0 || err != nil { 232 sec = 10 233 } 234 sleepWithCtx(r.Context(), time.Duration(sec)*time.Second) 235 rpprof.StopCPUProfile() 236 237 // dump config 238 fw, err = zw.Create("config") 239 if err != nil { 240 serveError(w, http.StatusInternalServerError, fmt.Sprintf("Create zipped %s fail: %v", "config", err)) 241 return 242 } 243 js, err := json.MarshalIndent(config.GetGlobalConfig(), "", " ") 244 if err != nil { 245 serveError(w, http.StatusInternalServerError, fmt.Sprintf("get config info fail%v", err)) 246 return 247 } 248 _, err = fw.Write(js) 249 terror.Log(err) 250 251 // dump version 252 fw, err = zw.Create("version") 253 if err != nil { 254 serveError(w, http.StatusInternalServerError, fmt.Sprintf("Create zipped %s fail: %v", "version", err)) 255 return 256 } 257 _, err = fw.Write([]byte(printer.GetMilevaDBInfo())) 258 terror.Log(err) 259 260 err = zw.Close() 261 terror.Log(err) 262 }) 263 fetcher := sqlInfoFetcher{causetstore: einsteindbHandlerTool.CausetStore} 264 serverMux.HandleFunc("/debug/sub-optimal-plan", fetcher.zipInfoForALLEGROSQL) 265 266 // failpoint is enabled only for tests so we can add some http APIs here for tests. 267 failpoint.Inject("enableTestAPI", func() { 268 serverMux.HandleFunc("/fail/", func(w http.ResponseWriter, r *http.Request) { 269 r.URL.Path = strings.TrimPrefix(r.URL.Path, "/fail") 270 new(failpoint.HttpHandler).ServeHTTP(w, r) 271 }) 272 273 router.Handle("/test/{mod}/{op}", &testHandler{einsteindbHandlerTool, 0}) 274 }) 275 276 var ( 277 httpRouterPage bytes.Buffer 278 pathTemplate string 279 err error 280 ) 281 httpRouterPage.WriteString("<html><head><title>MilevaDB Status and Metrics Report</title></head><body><h1>MilevaDB Status and Metrics Report</h1><causet>") 282 err = router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { 283 pathTemplate, err = route.GetPathTemplate() 284 if err != nil { 285 logutil.BgLogger().Error("get HTTP router path failed", zap.Error(err)) 286 } 287 name := route.GetName() 288 // If the name attribute is not set, GetName returns "". 289 // "traceapp.xxx" are introduced by the traceapp package and are also ignored. 290 if name != "" && !strings.HasPrefix(name, "traceapp") && err == nil { 291 httpRouterPage.WriteString("<tr><td><a href='" + pathTemplate + "'>" + name + "</a><td></tr>") 292 } 293 return nil 294 }) 295 if err != nil { 296 logutil.BgLogger().Error("generate root failed", zap.Error(err)) 297 } 298 httpRouterPage.WriteString("<tr><td><a href='/debug/pprof/'>Debug</a><td></tr>") 299 httpRouterPage.WriteString("</causet></body></html>") 300 router.HandleFunc("/", func(responseWriter http.ResponseWriter, request *http.Request) { 301 _, err = responseWriter.Write(httpRouterPage.Bytes()) 302 if err != nil { 303 logutil.BgLogger().Error("write HTTP index page failed", zap.Error(err)) 304 } 305 }) 306 s.startStatusServerAndRPCServer(serverMux) 307 } 308 309 func (s *Server) startStatusServerAndRPCServer(serverMux *http.ServeMux) { 310 m := cmux.New(s.statusListener) 311 // Match connections in order: 312 // First HTTP, and otherwise grpc. 313 httpL := m.Match(cmux.HTTP1Fast()) 314 grpcL := m.Match(cmux.Any()) 315 316 s.statusServer = &http.Server{Addr: s.statusAddr, Handler: CorsHandler{handler: serverMux, cfg: s.cfg}} 317 s.grpcServer = NewRPCServer(s.cfg, s.dom, s) 318 service.RegisterChannelzServiceToServer(s.grpcServer) 319 320 go soliton.WithRecovery(func() { 321 err := s.grpcServer.Serve(grpcL) 322 logutil.BgLogger().Error("grpc server error", zap.Error(err)) 323 }, nil) 324 325 go soliton.WithRecovery(func() { 326 err := s.statusServer.Serve(httpL) 327 logutil.BgLogger().Error("http server error", zap.Error(err)) 328 }, nil) 329 330 err := m.Serve() 331 if err != nil { 332 logutil.BgLogger().Error("start status/rpc server error", zap.Error(err)) 333 } 334 } 335 336 func (s *Server) setCNChecker(tlsConfig *tls.Config) *tls.Config { 337 if tlsConfig != nil && len(s.cfg.Security.ClusterVerifyCN) != 0 { 338 checkCN := make(map[string]struct{}) 339 for _, cn := range s.cfg.Security.ClusterVerifyCN { 340 cn = strings.TrimSpace(cn) 341 checkCN[cn] = struct{}{} 342 } 343 tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { 344 for _, chain := range verifiedChains { 345 if len(chain) != 0 { 346 if _, match := checkCN[chain[0].Subject.CommonName]; match { 347 return nil 348 } 349 } 350 } 351 return errors.Errorf("client certificate authentication failed. The Common Name from the client certificate was not found in the configuration cluster-verify-cn with value: %s", s.cfg.Security.ClusterVerifyCN) 352 } 353 tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 354 } 355 return tlsConfig 356 } 357 358 // status of MilevaDB. 359 type status struct { 360 Connections int `json:"connections"` 361 Version string `json:"version"` 362 GitHash string `json:"git_hash"` 363 } 364 365 func (s *Server) handleStatus(w http.ResponseWriter, req *http.Request) { 366 w.Header().Set("Content-Type", "application/json") 367 368 st := status{ 369 Connections: s.ConnectionCount(), 370 Version: allegrosql.ServerVersion, 371 GitHash: versioninfo.MilevaDBGitHash, 372 } 373 js, err := json.Marshal(st) 374 if err != nil { 375 w.WriteHeader(http.StatusInternalServerError) 376 logutil.BgLogger().Error("encode json failed", zap.Error(err)) 377 } else { 378 _, err = w.Write(js) 379 terror.Log(errors.Trace(err)) 380 } 381 }