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  }