Skip to content

Commit

Permalink
api: add /api/debug/health (pingcap#193)
Browse files Browse the repository at this point in the history
  • Loading branch information
xhebox committed Mar 13, 2023
1 parent f4262b6 commit e01a014
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 62 deletions.
44 changes: 44 additions & 0 deletions lib/cli/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2023 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cli

import (
"net/http"

"github.com/spf13/cobra"
)

const (
healthPrefix = "/api/debug/health"
)

func GetHealthCmd(ctx *Context) *cobra.Command {
rootCmd := &cobra.Command{
Use: "health",
Short: "",
}

rootCmd.RunE = func(cmd *cobra.Command, args []string) error {
resp, err := doRequest(cmd.Context(), ctx, http.MethodGet, healthPrefix, nil)
if err != nil {
return err
}

cmd.Println(resp)
return nil
}

return rootCmd
}
1 change: 1 addition & 0 deletions lib/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,6 @@ func GetRootCmd(tlsConfig *tls.Config) *cobra.Command {

rootCmd.AddCommand(GetNamespaceCmd(ctx))
rootCmd.AddCommand(GetConfigCmd(ctx))
rootCmd.AddCommand(GetHealthCmd(ctx))
return rootCmd
}
2 changes: 1 addition & 1 deletion pkg/server/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"go.uber.org/zap"
)

func Register(group *gin.RouterGroup, cfg config.API, logger *zap.Logger, nsmgr *mgrns.NamespaceManager, cfgmgr *mgrcfg.ConfigManager) {
func register(group *gin.RouterGroup, cfg config.API, logger *zap.Logger, nsmgr *mgrns.NamespaceManager, cfgmgr *mgrcfg.ConfigManager) {
{
adminGroup := group.Group("admin")
if cfg.EnableBasicAuth {
Expand Down
5 changes: 5 additions & 0 deletions pkg/server/api/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ type debugHttpHandler struct {
nsmgr *mgrns.NamespaceManager
}

func (h *debugHttpHandler) Health(c *gin.Context) {
c.JSON(http.StatusOK, "")
}

func (h *debugHttpHandler) Redirect(c *gin.Context) {
errs := h.nsmgr.RedirectConnections()
if len(errs) != 0 {
Expand All @@ -48,5 +52,6 @@ func (h *debugHttpHandler) Redirect(c *gin.Context) {
func registerDebug(group *gin.RouterGroup, logger *zap.Logger, nsmgr *mgrns.NamespaceManager) {
handler := &debugHttpHandler{logger, nsmgr}
group.POST("/redirect", handler.Redirect)
group.GET("/health", handler.Health)
pprof.RouteRegister(group, "/pprof")
}
112 changes: 112 additions & 0 deletions pkg/server/api/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2023 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import (
"crypto/tls"
"net"
"net/http"
"time"

ginzap "github.com/gin-contrib/zap"
"github.com/gin-gonic/gin"
"github.com/pingcap/TiProxy/lib/config"
"github.com/pingcap/TiProxy/lib/util/errors"
"github.com/pingcap/TiProxy/lib/util/waitgroup"
mgrcrt "github.com/pingcap/TiProxy/pkg/manager/cert"
mgrcfg "github.com/pingcap/TiProxy/pkg/manager/config"
mgrns "github.com/pingcap/TiProxy/pkg/manager/namespace"
"go.uber.org/atomic"
"go.uber.org/ratelimit"
"go.uber.org/zap"
)

const (
// DefAPILimit is the global API limit per second.
DefAPILimit = 100
// DefConnTimeout is used as timeout duration in the HTTP server.
DefConnTimeout = 30 * time.Second
)

type HTTPHandler interface {
RegisterHTTP(c *gin.Engine) error
}

type HTTPServer struct {
listener net.Listener
wg waitgroup.WaitGroup
}

func NewHTTPServer(cfg config.API, lg *zap.Logger,
nsmgr *mgrns.NamespaceManager, cfgmgr *mgrcfg.ConfigManager,
crtmgr *mgrcrt.CertManager, handler HTTPHandler,
ready *atomic.Bool) (*HTTPServer, error) {
h := &HTTPServer{}

var err error
h.listener, err = net.Listen("tcp", cfg.Addr)
if err != nil {
return nil, err
}

gin.SetMode(gin.ReleaseMode)
engine := gin.New()
limit := ratelimit.New(DefAPILimit)
engine.Use(
gin.Recovery(),
ginzap.GinzapWithConfig(lg.Named("gin"), &ginzap.Config{
UTC: true,
SkipPaths: []string{
"/api/debug/health",
},
}),
func(c *gin.Context) {
_ = limit.Take()
if !ready.Load() {
c.Abort()
c.JSON(http.StatusInternalServerError, "service not ready")
}
},
)

register(engine.Group("/api"), cfg, lg, nsmgr, cfgmgr)

if tlscfg := crtmgr.ServerTLS(); tlscfg != nil {
h.listener = tls.NewListener(h.listener, tlscfg)
}

if handler != nil {
if err := handler.RegisterHTTP(engine); err != nil {
return nil, errors.WithStack(err)
}
}

h.wg.Run(func() {
hsrv := http.Server{
Handler: engine.Handler(),
ReadHeaderTimeout: DefConnTimeout,
IdleTimeout: DefConnTimeout,
}
lg.Info("HTTP closed", zap.Error(hsrv.Serve(h.listener)))
})

return h, nil
}

func (h *HTTPServer) Close() error {
err := h.listener.Close()
h.wg.Wait()
return err
}
68 changes: 7 additions & 61 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,8 @@ package server

import (
"context"
"crypto/tls"
"net"
"net/http"
"time"

ginzap "github.com/gin-contrib/zap"
"github.com/gin-gonic/gin"
"github.com/pingcap/TiProxy/lib/config"
"github.com/pingcap/TiProxy/lib/util/errors"
"github.com/pingcap/TiProxy/lib/util/waitgroup"
Expand All @@ -38,17 +33,9 @@ import (
"github.com/pingcap/TiProxy/pkg/server/api"
clientv3 "go.etcd.io/etcd/client/v3"
"go.uber.org/atomic"
"go.uber.org/ratelimit"
"go.uber.org/zap"
)

const (
// DefAPILimit is the global API limit per second.
DefAPILimit = 100
// DefConnTimeout is used as timeout duration in the HTTP server.
DefConnTimeout = 30 * time.Second
)

type Server struct {
wg waitgroup.WaitGroup
// managers
Expand All @@ -61,7 +48,7 @@ type Server struct {
// HTTP client
Http *http.Client
// HTTP server
HTTPListener net.Listener
HTTPServer *api.HTTPServer
// L7 proxy
Proxy *proxy.SQLServer
}
Expand All @@ -83,6 +70,7 @@ func NewServer(ctx context.Context, sctx *sctx.Context) (srv *Server, err error)
if srv.LoggerManager, lg, err = logger.NewLoggerManager(&sctx.Overlay.Log); err != nil {
return
}
srv.LoggerManager.Init(srv.ConfigManager.WatchConfig())

// setup config manager
if err = srv.ConfigManager.Init(ctx, lg.Named("config"), sctx.ConfigFile, &sctx.Overlay); err != nil {
Expand All @@ -91,9 +79,6 @@ func NewServer(ctx context.Context, sctx *sctx.Context) (srv *Server, err error)
}
cfg := srv.ConfigManager.GetConfig()

// also hook logger
srv.LoggerManager.Init(srv.ConfigManager.WatchConfig())

// setup metrics
srv.MetricsManager.Init(ctx, lg.Named("metrics"), cfg.Metrics.MetricsAddr, cfg.Metrics.MetricsInterval, cfg.Proxy.Addr)

Expand All @@ -102,48 +87,9 @@ func NewServer(ctx context.Context, sctx *sctx.Context) (srv *Server, err error)
return
}

// setup gin
{
slogger := lg.Named("gin")
gin.SetMode(gin.ReleaseMode)
engine := gin.New()
limit := ratelimit.New(DefAPILimit)
engine.Use(
gin.Recovery(),
ginzap.Ginzap(slogger, "", true),
func(c *gin.Context) {
_ = limit.Take()
if !ready.Load() {
c.Abort()
c.JSON(http.StatusInternalServerError, "service not ready")
}
},
)

api.Register(engine.Group("/api"), cfg.API, lg.Named("api"), srv.NamespaceManager, srv.ConfigManager)

srv.HTTPListener, err = net.Listen("tcp", cfg.API.Addr)
if err != nil {
return nil, err
}
if tlscfg := srv.CertManager.ServerTLS(); tlscfg != nil {
srv.HTTPListener = tls.NewListener(srv.HTTPListener, tlscfg)
}

if handler != nil {
if err := handler.RegisterHTTP(engine); err != nil {
return nil, errors.WithStack(err)
}
}

srv.wg.Run(func() {
hsrv := http.Server{
Handler: engine.Handler(),
ReadHeaderTimeout: DefConnTimeout,
IdleTimeout: DefConnTimeout,
}
slogger.Info("HTTP closed", zap.Error(hsrv.Serve(srv.HTTPListener)))
})
// setup http
if srv.HTTPServer, err = api.NewHTTPServer(cfg.API, lg.Named("api"), srv.NamespaceManager, srv.ConfigManager, srv.CertManager, handler, ready); err != nil {
return
}

// general cluster HTTP client
Expand Down Expand Up @@ -217,8 +163,8 @@ func (s *Server) Close() error {
if s.Proxy != nil {
errs = append(errs, s.Proxy.Close())
}
if s.HTTPListener != nil {
s.HTTPListener.Close()
if s.HTTPServer != nil {
s.HTTPServer.Close()
}
if s.NamespaceManager != nil {
errs = append(errs, s.NamespaceManager.Close())
Expand Down

0 comments on commit e01a014

Please sign in to comment.