github.com/XiaoMi/Gaea@v1.2.5/proxy/server/admin.go (about)

     1  // Copyright 2019 The Gaea Authors. All Rights Reserved.
     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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package server
    16  
    17  import (
    18  	"fmt"
    19  	"net"
    20  	"net/http"
    21  	"net/http/pprof"
    22  	"os"
    23  	"os/exec"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/XiaoMi/Gaea/log"
    28  	"github.com/XiaoMi/Gaea/models"
    29  	"github.com/XiaoMi/Gaea/util"
    30  	"github.com/gin-contrib/gzip"
    31  	"github.com/gin-gonic/gin"
    32  )
    33  
    34  const (
    35  	selfDefinedInternalError = 800
    36  )
    37  
    38  // SQLFingerprint sql fingerprint
    39  type SQLFingerprint struct {
    40  	SlowSQL  map[string]string `json:"slow_sql"`
    41  	ErrorSQL map[string]string `json:"error_sql"`
    42  }
    43  
    44  // AdminServer means admin server
    45  type AdminServer struct {
    46  	exit struct {
    47  		C chan struct{}
    48  	}
    49  	proxy *Server
    50  	model *models.ProxyInfo
    51  
    52  	listener      net.Listener
    53  	adminUser     string
    54  	adminPassword string
    55  	engine        *gin.Engine
    56  
    57  	configType          string
    58  	coordinatorAddr     string
    59  	coordinatorUsername string
    60  	coordinatorPassword string
    61  	coordinatorRoot     string
    62  }
    63  
    64  // NewAdminServer create new admin server
    65  func NewAdminServer(proxy *Server, cfg *models.Proxy) (*AdminServer, error) {
    66  	var err error
    67  	s := new(AdminServer)
    68  
    69  	// if error occurs, recycle the resources during creation.
    70  	defer func() {
    71  		if e := recover(); e != nil {
    72  			err = fmt.Errorf("NewAdminServer panic: %v", e)
    73  		}
    74  
    75  		if err != nil {
    76  			s.Close()
    77  		}
    78  	}()
    79  
    80  	s.exit.C = make(chan struct{})
    81  	s.proxy = proxy
    82  	s.adminUser = cfg.AdminUser
    83  	s.adminPassword = cfg.AdminPassword
    84  	s.configType = cfg.ConfigType
    85  	s.coordinatorAddr = cfg.CoordinatorAddr
    86  	s.coordinatorUsername = cfg.UserName
    87  	s.coordinatorPassword = cfg.Password
    88  	s.coordinatorRoot = cfg.CoordinatorRoot
    89  
    90  	s.engine = gin.New()
    91  	l, err := net.Listen(cfg.ProtoType, cfg.AdminAddr)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	s.listener = l
    96  	s.registerURL()
    97  	s.registerMetric()
    98  	s.registerProf()
    99  
   100  	proxyInfo, err := NewProxyInfo(cfg, s.proxy.Listener().Addr().String())
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	s.model = proxyInfo
   105  
   106  	if err = s.registerProxy(); err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	log.Notice("[server] NewAdminServer, Api Server running, netProto: http, addr: %s", cfg.AdminAddr)
   111  	return s, nil
   112  }
   113  
   114  // Run run admin server
   115  func (s *AdminServer) Run() {
   116  	defer s.listener.Close()
   117  
   118  	eh := make(chan error, 1)
   119  	go func(l net.Listener) {
   120  		h := http.NewServeMux()
   121  		h.Handle("/", s.engine)
   122  		hs := &http.Server{Handler: h}
   123  		eh <- hs.Serve(s.listener)
   124  	}(s.listener)
   125  
   126  	select {
   127  	case <-s.exit.C:
   128  		log.Warn("[%p] admin shutdown", s)
   129  	case err := <-eh:
   130  		log.Fatal("[%p] admin exit on error:%v", s, err)
   131  	}
   132  }
   133  
   134  // Close close admin server
   135  func (s *AdminServer) Close() error {
   136  	close(s.exit.C)
   137  	if err := s.unregisterProxy(); err != nil {
   138  		log.Fatal("unregister proxy failed, %v", err)
   139  		return err
   140  	}
   141  	return nil
   142  }
   143  
   144  func (s *AdminServer) registerURL() {
   145  	adminGroup := s.engine.Group("/api/proxy", gin.BasicAuth(gin.Accounts{s.adminUser: s.adminPassword}))
   146  	adminGroup.GET("/ping", s.ping)
   147  	adminGroup.PUT("/config/prepare/:name", s.prepareConfig)
   148  	adminGroup.PUT("/config/commit/:name", s.commitConfig)
   149  	adminGroup.PUT("/namespace/delete/:name", s.deleteNamespace)
   150  	adminGroup.GET("/config/fingerprint", s.configFingerprint)
   151  
   152  	adminGroup.GET("/stats/sessionsqlfingerprint/:namespace", s.getNamespaceSessionSQLFingerprint)
   153  	adminGroup.GET("/stats/backendsqlfingerprint/:namespace", s.getNamespaceBackendSQLFingerprint)
   154  	adminGroup.DELETE("/stats/sessionsqlfingerprint/:namespace", s.clearNamespaceSessionSQLFingerprint)
   155  	adminGroup.DELETE("/stats/backendsqlfingerprint/:namespace", s.clearNamespaceBackendSQLFingerprint)
   156  
   157  	adminGroup.Use(gzip.Gzip(gzip.DefaultCompression))
   158  	adminGroup.Use(gin.Recovery())
   159  	adminGroup.Use(func(c *gin.Context) {
   160  		c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
   161  	})
   162  }
   163  
   164  // @Summary 获取proxy prometheus指标信息
   165  // @Description 获取gaea proxy prometheus指标信息
   166  // @Security BasicAuth
   167  // @Router /api/metric/metrics [get]
   168  func (s *AdminServer) registerMetric() {
   169  	metricGroup := s.engine.Group("/api/metric", gin.BasicAuth(gin.Accounts{s.adminUser: s.adminPassword}))
   170  	for path, handler := range s.proxy.manager.GetStatisticManager().GetHandlers() {
   171  		log.Debug("[server] AdminServer got metric handler, path: %s", path)
   172  		metricGroup.GET(path, gin.WrapH(handler))
   173  	}
   174  }
   175  
   176  func (s *AdminServer) registerProf() {
   177  	profGroup := s.engine.Group("/debug/pprof", gin.BasicAuth(gin.Accounts{s.adminUser: s.adminPassword}))
   178  	profGroup.GET("/", gin.WrapF(pprof.Index))
   179  	profGroup.GET("/cmdline", gin.WrapF(pprof.Cmdline))
   180  	profGroup.GET("/profile", gin.WrapF(pprof.Profile))
   181  	profGroup.POST("/symbol", gin.WrapF(pprof.Symbol))
   182  	profGroup.GET("/symbol", gin.WrapF(pprof.Symbol))
   183  	profGroup.GET("/trace", gin.WrapF(pprof.Trace))
   184  	profGroup.GET("/block", gin.WrapF(pprof.Handler("block").ServeHTTP))
   185  	profGroup.GET("/goroutine", gin.WrapF(pprof.Handler("goroutine").ServeHTTP))
   186  	profGroup.GET("/heap", gin.WrapF(pprof.Handler("heap").ServeHTTP))
   187  	profGroup.GET("/mutex", gin.WrapF(pprof.Handler("mutex").ServeHTTP))
   188  	profGroup.GET("/threadcreate", gin.WrapF(pprof.Handler("threadcreate").ServeHTTP))
   189  	profGroup.GET("/allocs", gin.WrapF(pprof.Handler("allocs").ServeHTTP))
   190  }
   191  
   192  // NewProxyInfo create proxy information
   193  func NewProxyInfo(cfg *models.Proxy, addr string) (*models.ProxyInfo, error) {
   194  	ipPort, err := util.ResolveAddr(cfg.ProtoType, addr)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	proxyIPPort := strings.Split(cfg.ProxyAddr, ":")
   200  	adminIPPort := strings.Split(cfg.AdminAddr, ":")
   201  
   202  	proxyInfo := &models.ProxyInfo{
   203  		StartTime: time.Now().String(),
   204  		ProtoType: cfg.ProtoType,
   205  		ProxyPort: proxyIPPort[1],
   206  		AdminPort: adminIPPort[1],
   207  	}
   208  	tmp := strings.Split(ipPort, ":")
   209  	proxyInfo.IP = tmp[0]
   210  	proxyInfo.Pid = os.Getpid()
   211  	proxyInfo.Pwd, _ = os.Getwd()
   212  	o, err := exec.Command("uname", "-a").Output()
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	proxyInfo.Sys = strings.TrimSpace(string(o))
   217  
   218  	x, err := generateToken(cfg.ProtoType, addr)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  	proxyInfo.Token = x
   223  
   224  	return proxyInfo, nil
   225  }
   226  
   227  func generateToken(protoType, addr string) (string, error) {
   228  	ipPort, err := util.ResolveAddr(protoType, addr)
   229  	if err != nil {
   230  		return "", err
   231  	}
   232  	return ipPort, nil
   233  }
   234  
   235  func (s *AdminServer) registerProxy() error {
   236  	// 如果设定值 s.configType 是为文档 File 就直接回传
   237  	if s.configType == models.ConfigFile {
   238  		return nil
   239  	}
   240  	// 目前设定值 s.configType 可能为 models.ConfigEtcd 和 models.ConfigEtcdV3 两种
   241  	client := models.NewClient(s.configType, s.coordinatorAddr, s.coordinatorUsername, s.coordinatorPassword, s.coordinatorRoot)
   242  	store := models.NewStore(client)
   243  	defer store.Close()
   244  	if err := store.CreateProxy(s.model); err != nil {
   245  		return err
   246  	}
   247  	return nil
   248  }
   249  
   250  func (s *AdminServer) unregisterProxy() error {
   251  	if s.configType == models.ConfigFile {
   252  		return nil
   253  	}
   254  	client := models.NewClient(s.configType, s.coordinatorAddr, s.coordinatorUsername, s.coordinatorPassword, s.coordinatorRoot)
   255  	store := models.NewStore(client)
   256  	defer store.Close()
   257  	if err := store.DeleteProxy(s.model.Token); err != nil {
   258  		return err
   259  	}
   260  	return nil
   261  }
   262  
   263  // @Summary 获取proxy admin接口状态
   264  // @Description  获取proxy admin接口状态
   265  // @Success 200 {string} string "OK"
   266  // @Security BasicAuth
   267  // @Router /api/proxy/ping [get]
   268  func (s *AdminServer) ping(c *gin.Context) {
   269  	c.JSON(http.StatusOK, "OK")
   270  }
   271  
   272  // @Summary prepare namespace配置
   273  // @Description 通过管理接口, 二阶段提交, prepare namespace配置
   274  // @Produce  json
   275  // @Param name path string true "namespace name"
   276  // @Success 200 {string} string "OK"
   277  // @Security BasicAuth
   278  // @Router /api/proxy/config/prepare/{name} [put]
   279  func (s *AdminServer) prepareConfig(c *gin.Context) {
   280  	name := strings.TrimSpace(c.Param("name"))
   281  	if name == "" {
   282  		c.JSON(selfDefinedInternalError, "missing namespace name")
   283  		return
   284  	}
   285  	client := models.NewClient(s.configType, s.coordinatorAddr, s.coordinatorUsername, s.coordinatorPassword, s.coordinatorRoot)
   286  	defer client.Close()
   287  	err := s.proxy.ReloadNamespacePrepare(name, client)
   288  	if err != nil {
   289  		log.Warn("prepare config of namespace: %s failed, err: %v", name, err)
   290  		c.JSON(selfDefinedInternalError, err.Error())
   291  		return
   292  	}
   293  	c.JSON(http.StatusOK, "OK")
   294  }
   295  
   296  // @Summary commit namespace配置
   297  // @Description 通过管理接口, 二阶段提交, commit namespace配置, 使etcd配置生效
   298  // @Produce  json
   299  // @Param name path string true "namespace name"
   300  // @Success 200 {string} string "OK"
   301  // @Security BasicAuth
   302  // @Router /api/proxy/config/commit/{name} [put]
   303  func (s *AdminServer) commitConfig(c *gin.Context) {
   304  	name := strings.TrimSpace(c.Param("name"))
   305  	if name == "" {
   306  		c.JSON(selfDefinedInternalError, "missing namespace name")
   307  		return
   308  	}
   309  	err := s.proxy.ReloadNamespaceCommit(name)
   310  	if err != nil {
   311  		c.JSON(selfDefinedInternalError, err.Error())
   312  		return
   313  	}
   314  	c.JSON(http.StatusOK, "OK")
   315  }
   316  
   317  // @Summary 删除namespace配置
   318  // @Description 通过管理接口删除指定namespace配置
   319  // @Produce  json
   320  // @Param name path string true "namespace name"
   321  // @Success 200 {string} string "OK"
   322  // @Security BasicAuth
   323  // @Router /api/proxy/config/delete/{name} [put]
   324  func (s *AdminServer) deleteNamespace(c *gin.Context) {
   325  	name := strings.TrimSpace(c.Param("name"))
   326  	if name == "" {
   327  		c.JSON(selfDefinedInternalError, "missing namespace name")
   328  		return
   329  	}
   330  	// delete namespace
   331  	err := s.proxy.DeleteNamespace(name)
   332  	if err != nil {
   333  		c.JSON(selfDefinedInternalError, err.Error())
   334  		return
   335  	}
   336  	c.JSON(http.StatusOK, "OK")
   337  }
   338  
   339  // @Summary 返回配置指纹
   340  // @Description 返回配置指纹, 指纹随配置变化而变化
   341  // @Produce  json
   342  // @Success 200 {string} string "Config Fingerprint"
   343  // @Security BasicAuth
   344  // @Router /api/proxy/config/fingerprint [get]
   345  func (s *AdminServer) configFingerprint(c *gin.Context) {
   346  	c.JSON(http.StatusOK, s.proxy.manager.ConfigFingerprint())
   347  }
   348  
   349  // @Summary 获取Porxy 慢SQL、错误SQL信息
   350  // @Description 通过管理接口获取Porxy 慢SQL、错误SQL信息
   351  // @Produce  json
   352  // @Param namespace path string true "namespace name"
   353  // @Success 200 {object} SQLFingerprint
   354  // @Security BasicAuth
   355  // @Router /api/proxy/stats/sessionsqlfingerprint/{namespace} [get]
   356  func (s *AdminServer) getNamespaceSessionSQLFingerprint(c *gin.Context) {
   357  	ns := strings.TrimSpace(c.Param("namespace"))
   358  	namespace := s.proxy.manager.GetNamespace(ns)
   359  	if namespace == nil {
   360  		c.JSON(selfDefinedInternalError, "namespace not found")
   361  		return
   362  	}
   363  
   364  	slowSQLFingerprints := namespace.GetSlowSQLFingerprints()
   365  	errSQLFingerprints := namespace.GetErrorSQLFingerprints()
   366  	ret := &SQLFingerprint{SlowSQL: slowSQLFingerprints, ErrorSQL: errSQLFingerprints}
   367  
   368  	c.JSON(http.StatusOK, ret)
   369  }
   370  
   371  // @Summary 获取后端节点慢SQL、错误SQL信息
   372  // @Description 通过管理接口获取后端节点慢SQL、错误SQL信息
   373  // @Produce  json
   374  // @Param namespace path string true "namespace name"
   375  // @Success 200 {object} SQLFingerprint
   376  // @Security BasicAuth
   377  // @Router /api/proxy/stats/backendsqlfingerprint/{namespace} [get]
   378  func (s *AdminServer) getNamespaceBackendSQLFingerprint(c *gin.Context) {
   379  	ns := strings.TrimSpace(c.Param("namespace"))
   380  	namespace := s.proxy.manager.GetNamespace(ns)
   381  	if namespace == nil {
   382  		c.JSON(selfDefinedInternalError, "namespace not found")
   383  		return
   384  	}
   385  
   386  	slowSQLFingerprints := namespace.GetBackendSlowSQLFingerprints()
   387  	errSQLFingerprints := namespace.GetBackendErrorSQLFingerprints()
   388  	ret := &SQLFingerprint{SlowSQL: slowSQLFingerprints, ErrorSQL: errSQLFingerprints}
   389  
   390  	c.JSON(http.StatusOK, ret)
   391  }
   392  
   393  // @Summary 清空Porxy节点慢SQL、错误SQL信息
   394  // @Description 通过管理接口清空Porxy慢SQL、错误SQL信息
   395  // @Produce  json
   396  // @Param namespace path string true "namespace name"
   397  // @Success 200 {object} SQLFingerprint
   398  // @Security BasicAuth
   399  // @Router /api/proxy/stats/sessionsqlfingerprint/{namespace} [delete]
   400  func (s *AdminServer) clearNamespaceSessionSQLFingerprint(c *gin.Context) {
   401  	ns := strings.TrimSpace(c.Param("namespace"))
   402  	namespace := s.proxy.manager.GetNamespace(ns)
   403  	if namespace == nil {
   404  		c.JSON(selfDefinedInternalError, "namespace not found")
   405  		return
   406  	}
   407  
   408  	namespace.ClearSlowSQLFingerprints()
   409  	namespace.ClearErrorSQLFingerprints()
   410  
   411  	c.JSON(http.StatusOK, "OK")
   412  }
   413  
   414  // @Summary 清空后端节点慢SQL、错误SQL信息
   415  // @Description 通过管理接口清空后端节点慢SQL、错误SQL信息
   416  // @Produce  json
   417  // @Param namespace path string true "namespace name"
   418  // @Success 200 {object} SQLFingerprint
   419  // @Security BasicAuth
   420  // @Router /api/proxy/stats/backendsqlfingerprint/{namespace} [delete]
   421  func (s *AdminServer) clearNamespaceBackendSQLFingerprint(c *gin.Context) {
   422  	ns := strings.TrimSpace(c.Param("namespace"))
   423  	namespace := s.proxy.manager.GetNamespace(ns)
   424  	if namespace == nil {
   425  		c.JSON(selfDefinedInternalError, "namespace not found")
   426  		return
   427  	}
   428  
   429  	namespace.ClearBackendSlowSQLFingerprints()
   430  	namespace.ClearBackendErrorSQLFingerprints()
   431  
   432  	c.JSON(http.StatusOK, "OK")
   433  }