Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: add /api/debug/health #193

Merged
merged 4 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 2022 PingCAP, Inc.
xhebox marked this conversation as resolved.
Show resolved Hide resolved
//
// 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