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 }