diff --git a/pkg/mcs/resourcemanager/server/apis/v1/api.go b/pkg/mcs/resourcemanager/server/apis/v1/api.go index 7b5f2903484..93738664361 100644 --- a/pkg/mcs/resourcemanager/server/apis/v1/api.go +++ b/pkg/mcs/resourcemanager/server/apis/v1/api.go @@ -82,6 +82,7 @@ func NewService(srv *rmserver.Service) *Service { c.Next() }) apiHandlerEngine.GET("metrics", utils.PromHandler()) + apiHandlerEngine.GET("status", utils.StatusHandler) pprof.Register(apiHandlerEngine) endpoint := apiHandlerEngine.Group(APIPathPrefix) endpoint.Use(multiservicesapi.ServiceRedirector()) diff --git a/pkg/mcs/scheduling/server/apis/v1/api.go b/pkg/mcs/scheduling/server/apis/v1/api.go index e6881f2f85c..8c4ed015a5c 100644 --- a/pkg/mcs/scheduling/server/apis/v1/api.go +++ b/pkg/mcs/scheduling/server/apis/v1/api.go @@ -107,6 +107,7 @@ func NewService(srv *scheserver.Service) *Service { c.Next() }) apiHandlerEngine.GET("metrics", mcsutils.PromHandler()) + apiHandlerEngine.GET("status", mcsutils.StatusHandler) pprof.Register(apiHandlerEngine) root := apiHandlerEngine.Group(APIPathPrefix) root.Use(multiservicesapi.ServiceRedirector()) diff --git a/pkg/mcs/server/server.go b/pkg/mcs/server/server.go index 95b806e20ef..a8dedd8ad91 100644 --- a/pkg/mcs/server/server.go +++ b/pkg/mcs/server/server.go @@ -162,3 +162,8 @@ func (bs *BaseServer) GetListener() net.Listener { func (bs *BaseServer) IsSecure() bool { return bs.secure } + +// StartTimestamp returns the start timestamp of this server +func (bs *BaseServer) StartTimestamp() int64 { + return bs.startTimestamp +} diff --git a/pkg/mcs/tso/server/apis/v1/api.go b/pkg/mcs/tso/server/apis/v1/api.go index e5f0dfb5440..94282592151 100644 --- a/pkg/mcs/tso/server/apis/v1/api.go +++ b/pkg/mcs/tso/server/apis/v1/api.go @@ -91,6 +91,7 @@ func NewService(srv *tsoserver.Service) *Service { c.Next() }) apiHandlerEngine.GET("metrics", utils.PromHandler()) + apiHandlerEngine.GET("status", utils.StatusHandler) pprof.Register(apiHandlerEngine) root := apiHandlerEngine.Group(APIPathPrefix) root.Use(multiservicesapi.ServiceRedirector()) diff --git a/pkg/mcs/utils/util.go b/pkg/mcs/utils/util.go index a0708f9bf88..6fa6c2cb08e 100644 --- a/pkg/mcs/utils/util.go +++ b/pkg/mcs/utils/util.go @@ -33,9 +33,11 @@ import ( "github.com/soheilhy/cmux" "github.com/tikv/pd/pkg/errs" "github.com/tikv/pd/pkg/utils/apiutil" + "github.com/tikv/pd/pkg/utils/apiutil/multiservicesapi" "github.com/tikv/pd/pkg/utils/etcdutil" "github.com/tikv/pd/pkg/utils/grpcutil" "github.com/tikv/pd/pkg/utils/logutil" + "github.com/tikv/pd/pkg/versioninfo" "go.etcd.io/etcd/clientv3" "go.etcd.io/etcd/pkg/types" "go.uber.org/zap" @@ -78,6 +80,19 @@ func PromHandler() gin.HandlerFunc { } } +// StatusHandler is a handler to get status info. +func StatusHandler(c *gin.Context) { + svr := c.MustGet(multiservicesapi.ServiceContextKey).(server) + version := versioninfo.Status{ + BuildTS: versioninfo.PDBuildTS, + GitHash: versioninfo.PDGitHash, + Version: versioninfo.PDReleaseVersion, + StartTimestamp: svr.StartTimestamp(), + } + + c.IndentedJSON(http.StatusOK, version) +} + type server interface { GetBackendEndpoints() string Context() context.Context @@ -97,6 +112,7 @@ type server interface { RegisterGRPCService(*grpc.Server) SetUpRestHandler() (http.Handler, apiutil.APIServiceGroup) diagnosticspb.DiagnosticsServer + StartTimestamp() int64 } // WaitAPIServiceReady waits for the api service ready. diff --git a/pkg/versioninfo/versioninfo.go b/pkg/versioninfo/versioninfo.go index 7b666334002..d6f4738d786 100644 --- a/pkg/versioninfo/versioninfo.go +++ b/pkg/versioninfo/versioninfo.go @@ -24,6 +24,15 @@ import ( "go.uber.org/zap" ) +// Status is the status of PD server. +// NOTE: This type is exported by HTTP API. Please pay more attention when modifying it. +type Status struct { + BuildTS string `json:"build_ts"` + Version string `json:"version"` + GitHash string `json:"git_hash"` + StartTimestamp int64 `json:"start_timestamp"` +} + const ( // CommunityEdition is the default edition for building. CommunityEdition = "Community" diff --git a/server/api/status.go b/server/api/status.go index 8f837ec019d..ba8f3b484dd 100644 --- a/server/api/status.go +++ b/server/api/status.go @@ -27,14 +27,6 @@ type statusHandler struct { rd *render.Render } -// NOTE: This type is exported by HTTP API. Please pay more attention when modifying it. -type status struct { - BuildTS string `json:"build_ts"` - Version string `json:"version"` - GitHash string `json:"git_hash"` - StartTimestamp int64 `json:"start_timestamp"` -} - func newStatusHandler(svr *server.Server, rd *render.Render) *statusHandler { return &statusHandler{ svr: svr, @@ -44,10 +36,10 @@ func newStatusHandler(svr *server.Server, rd *render.Render) *statusHandler { // @Summary Get the build info of PD server. // @Produce json -// @Success 200 {object} status +// @Success 200 {object} versioninfo.Status // @Router /status [get] func (h *statusHandler) GetPDStatus(w http.ResponseWriter, r *http.Request) { - version := status{ + version := versioninfo.Status{ BuildTS: versioninfo.PDBuildTS, GitHash: versioninfo.PDGitHash, Version: versioninfo.PDReleaseVersion, diff --git a/server/api/status_test.go b/server/api/status_test.go index 7017c4271a1..065618efb6c 100644 --- a/server/api/status_test.go +++ b/server/api/status_test.go @@ -24,7 +24,7 @@ import ( ) func checkStatusResponse(re *require.Assertions, body []byte) { - got := status{} + got := versioninfo.Status{} re.NoError(json.Unmarshal(body, &got)) re.Equal(versioninfo.PDBuildTS, got.BuildTS) re.Equal(versioninfo.PDGitHash, got.GitHash) diff --git a/tests/integrations/mcs/resourcemanager/server_test.go b/tests/integrations/mcs/resourcemanager/server_test.go index b7a9b0be218..d3837ad15f3 100644 --- a/tests/integrations/mcs/resourcemanager/server_test.go +++ b/tests/integrations/mcs/resourcemanager/server_test.go @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/require" "github.com/tikv/pd/client/grpcutil" "github.com/tikv/pd/pkg/utils/tempurl" + "github.com/tikv/pd/pkg/versioninfo" "github.com/tikv/pd/tests" ) @@ -102,4 +103,19 @@ func TestResourceManagerServer(t *testing.T) { re.NoError(err) re.Contains(string(respBytes), "resource_manager_server_info") } + + // Test status handler + { + resp, err := http.Get(addr + "/status") + re.NoError(err) + defer resp.Body.Close() + re.Equal(http.StatusOK, resp.StatusCode) + respBytes, err := io.ReadAll(resp.Body) + re.NoError(err) + var s versioninfo.Status + re.NoError(json.Unmarshal(respBytes, &s)) + re.Equal(versioninfo.PDBuildTS, s.BuildTS) + re.Equal(versioninfo.PDGitHash, s.GitHash) + re.Equal(versioninfo.PDReleaseVersion, s.Version) + } } diff --git a/tests/integrations/mcs/scheduling/api_test.go b/tests/integrations/mcs/scheduling/api_test.go index 867a7bfb5df..177f959b953 100644 --- a/tests/integrations/mcs/scheduling/api_test.go +++ b/tests/integrations/mcs/scheduling/api_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "io" "net/http" "testing" "time" @@ -22,6 +23,7 @@ import ( "github.com/tikv/pd/pkg/storage" "github.com/tikv/pd/pkg/utils/apiutil" "github.com/tikv/pd/pkg/utils/testutil" + "github.com/tikv/pd/pkg/versioninfo" "github.com/tikv/pd/tests" ) @@ -547,3 +549,45 @@ func (suite *apiTestSuite) checkFollowerForward(cluster *tests.TestCluster) { ) re.NoError(err) } + +func (suite *apiTestSuite) TestMetrics() { + suite.env.RunTestInAPIMode(suite.checkMetrics) +} + +func (suite *apiTestSuite) checkMetrics(cluster *tests.TestCluster) { + re := suite.Require() + s := cluster.GetSchedulingPrimaryServer() + testutil.Eventually(re, func() bool { + return s.IsServing() + }, testutil.WithWaitFor(5*time.Second), testutil.WithTickInterval(50*time.Millisecond)) + resp, err := http.Get(s.GetConfig().GetAdvertiseListenAddr() + "/metrics") + re.NoError(err) + defer resp.Body.Close() + re.Equal(http.StatusOK, resp.StatusCode) + respBytes, err := io.ReadAll(resp.Body) + re.NoError(err) + re.Contains(string(respBytes), "scheduling_server_info") +} + +func (suite *apiTestSuite) TestStatus() { + suite.env.RunTestInAPIMode(suite.checkStatus) +} + +func (suite *apiTestSuite) checkStatus(cluster *tests.TestCluster) { + re := suite.Require() + s := cluster.GetSchedulingPrimaryServer() + testutil.Eventually(re, func() bool { + return s.IsServing() + }, testutil.WithWaitFor(5*time.Second), testutil.WithTickInterval(50*time.Millisecond)) + resp, err := http.Get(s.GetConfig().GetAdvertiseListenAddr() + "/status") + re.NoError(err) + defer resp.Body.Close() + re.Equal(http.StatusOK, resp.StatusCode) + respBytes, err := io.ReadAll(resp.Body) + re.NoError(err) + var status versioninfo.Status + re.NoError(json.Unmarshal(respBytes, &status)) + re.Equal(versioninfo.PDBuildTS, status.BuildTS) + re.Equal(versioninfo.PDGitHash, status.GitHash) + re.Equal(versioninfo.PDReleaseVersion, status.Version) +} diff --git a/tests/integrations/mcs/tso/api_test.go b/tests/integrations/mcs/tso/api_test.go index 59073aca1e1..a5d68cfa90f 100644 --- a/tests/integrations/mcs/tso/api_test.go +++ b/tests/integrations/mcs/tso/api_test.go @@ -33,6 +33,7 @@ import ( "github.com/tikv/pd/pkg/storage/endpoint" "github.com/tikv/pd/pkg/utils/apiutil" "github.com/tikv/pd/pkg/utils/testutil" + "github.com/tikv/pd/pkg/versioninfo" "github.com/tikv/pd/server/config" "github.com/tikv/pd/tests" ) @@ -244,3 +245,33 @@ func TestForwardOnlyTSONoScheduling(t *testing.T) { testutil.Status(re, http.StatusInternalServerError), testutil.StringContain(re, "[PD:apiutil:ErrRedirect]redirect failed")) re.NoError(err) } + +func (suite *tsoAPITestSuite) TestMetrics() { + re := suite.Require() + + primary := suite.tsoCluster.WaitForDefaultPrimaryServing(re) + resp, err := http.Get(primary.GetConfig().GetAdvertiseListenAddr() + "/metrics") + re.NoError(err) + defer resp.Body.Close() + re.Equal(http.StatusOK, resp.StatusCode) + respBytes, err := io.ReadAll(resp.Body) + re.NoError(err) + re.Contains(string(respBytes), "tso_server_info") +} + +func (suite *tsoAPITestSuite) TestStatus() { + re := suite.Require() + + primary := suite.tsoCluster.WaitForDefaultPrimaryServing(re) + resp, err := http.Get(primary.GetConfig().GetAdvertiseListenAddr() + "/status") + re.NoError(err) + defer resp.Body.Close() + re.Equal(http.StatusOK, resp.StatusCode) + respBytes, err := io.ReadAll(resp.Body) + re.NoError(err) + var s versioninfo.Status + re.NoError(json.Unmarshal(respBytes, &s)) + re.Equal(versioninfo.PDBuildTS, s.BuildTS) + re.Equal(versioninfo.PDGitHash, s.GitHash) + re.Equal(versioninfo.PDReleaseVersion, s.Version) +} diff --git a/tests/integrations/mcs/tso/server_test.go b/tests/integrations/mcs/tso/server_test.go index 9dc9aa32368..a6a2c42acc9 100644 --- a/tests/integrations/mcs/tso/server_test.go +++ b/tests/integrations/mcs/tso/server_test.go @@ -576,18 +576,6 @@ func (suite *CommonTestSuite) TestAdvertiseAddr() { re.Equal(conf.GetListenAddr(), conf.GetAdvertiseListenAddr()) } -func (suite *CommonTestSuite) TestMetrics() { - re := suite.Require() - - resp, err := http.Get(suite.tsoDefaultPrimaryServer.GetConfig().GetAdvertiseListenAddr() + "/metrics") - re.NoError(err) - defer resp.Body.Close() - re.Equal(http.StatusOK, resp.StatusCode) - respBytes, err := io.ReadAll(resp.Body) - re.NoError(err) - re.Contains(string(respBytes), "tso_server_info") -} - func (suite *CommonTestSuite) TestBootstrapDefaultKeyspaceGroup() { re := suite.Require()