You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by mi...@apache.org on 2023/05/04 04:48:54 UTC

[shardingsphere-on-cloud] branch main updated: chore(pitr): agent add health check api, cli check duplicate agent ip.

This is an automated email from the ASF dual-hosted git repository.

miaoliyao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shardingsphere-on-cloud.git


The following commit(s) were added to refs/heads/main by this push:
     new 110fd9c  chore(pitr): agent add health check api, cli check duplicate agent ip.
     new 7a88262  Merge pull request #331 from Xu-Wentao/pitr
110fd9c is described below

commit 110fd9cc4e62203564c66f4784b24d1e12d6f040
Author: xuwentao <cu...@yahoo.com>
AuthorDate: Thu Apr 27 16:48:16 2023 +0800

    chore(pitr): agent add health check api, cli check duplicate agent ip.
---
 pitr/agent/internal/handler/handler_suite_test.go  |   1 +
 .../{handler_suite_test.go => healthcheck.go}      |  50 +++-----
 .../{handler_suite_test.go => healthcheck_test.go} |  46 +++----
 pitr/agent/internal/handler/view/healthcheck.go    |  25 ++++
 pitr/agent/main.go                                 |   2 +
 pitr/agent/pkg/gsutil/conn.go                      |   2 +-
 pitr/cli/internal/cmd/backup.go                    |  11 +-
 pitr/cli/internal/cmd/backup_test.go               |  11 +-
 pitr/cli/internal/cmd/restore_test.go              |   2 +-
 pitr/cli/internal/cmd/root.go                      |  25 +++-
 pitr/cli/internal/pkg/agent-server.go              | 142 ++++++---------------
 pitr/cli/internal/pkg/agent-server_test.go         |  17 ++-
 pitr/cli/internal/pkg/mocks/agent-server.go        |   8 +-
 pitr/cli/internal/pkg/model/as_healthcheck.go      |  25 ++++
 pitr/cli/internal/pkg/xerr/err.go                  |   6 +
 pitr/cli/pkg/httputils/mocks/req.go                |  26 +---
 pitr/cli/pkg/httputils/req.go                      |  32 +++--
 .../pkg/kubernetes/deployment/deployment_test.go   |   2 +-
 18 files changed, 213 insertions(+), 220 deletions(-)

diff --git a/pitr/agent/internal/handler/handler_suite_test.go b/pitr/agent/internal/handler/handler_suite_test.go
index 816ef69..30dbb4b 100644
--- a/pitr/agent/internal/handler/handler_suite_test.go
+++ b/pitr/agent/internal/handler/handler_suite_test.go
@@ -56,5 +56,6 @@ var _ = BeforeSuite(func() {
 	app.Route("/api", func(r fiber.Router) {
 		r.Post("/diskspace", handler.DiskSpace)
 		r.Delete("/backup", handler.DeleteBackup)
+		r.Post("/healthz", handler.HealthCheck)
 	})
 })
diff --git a/pitr/agent/internal/handler/handler_suite_test.go b/pitr/agent/internal/handler/healthcheck.go
similarity index 56%
copy from pitr/agent/internal/handler/handler_suite_test.go
copy to pitr/agent/internal/handler/healthcheck.go
index 816ef69..877adc0 100644
--- a/pitr/agent/internal/handler/handler_suite_test.go
+++ b/pitr/agent/internal/handler/healthcheck.go
@@ -15,46 +15,28 @@
  * limitations under the License.
  */
 
-package handler_test
+package handler
 
 import (
-	"os"
-	"testing"
+	"fmt"
 
-	"github.com/apache/shardingsphere-on-cloud/pitr/agent/internal/handler"
-	"github.com/apache/shardingsphere-on-cloud/pitr/agent/pkg/logging"
+	"github.com/apache/shardingsphere-on-cloud/pitr/agent/internal/cons"
+	"github.com/apache/shardingsphere-on-cloud/pitr/agent/internal/handler/view"
+	"github.com/apache/shardingsphere-on-cloud/pitr/agent/internal/pkg"
 	"github.com/apache/shardingsphere-on-cloud/pitr/agent/pkg/responder"
 	"github.com/gofiber/fiber/v2"
-	"github.com/golang/mock/gomock"
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	"go.uber.org/zap"
 )
 
-var (
-	app  *fiber.App
-	ctrl *gomock.Controller
-)
-
-func TestHandler(t *testing.T) {
-	RegisterFailHandler(Fail)
-	RunSpecs(t, "Handler Suite")
-}
+func HealthCheck(ctx *fiber.Ctx) error {
+	in := &view.HealthCheckIn{}
+	if err := ctx.BodyParser(in); err != nil {
+		return fmt.Errorf("body parse err=%s,wrap=%w", err, cons.BodyParseFailed)
+	}
 
-var _ = BeforeSuite(func() {
-	// init log
-	logging.Init(zap.DebugLevel)
+	if err := pkg.OG.Auth(in.Username, in.Password, in.DBName, in.DBPort); err != nil {
+		efmt := "pkg.OG.Auth failure[un=%s,pw.len=%d,db=%s],err=%w"
+		return fmt.Errorf(efmt, in.Username, len(in.Password), in.DBName, err)
+	}
 
-	Expect(os.Setenv("SHELL", "/bin/bash")).To(Succeed())
-
-	// init app
-	app = fiber.New()
-	app.Get("/ping", func(ctx *fiber.Ctx) error {
-		return responder.Success(ctx, "pong")
-	})
-
-	app.Route("/api", func(r fiber.Router) {
-		r.Post("/diskspace", handler.DiskSpace)
-		r.Delete("/backup", handler.DeleteBackup)
-	})
-})
+	return responder.Success(ctx, "")
+}
diff --git a/pitr/agent/internal/handler/handler_suite_test.go b/pitr/agent/internal/handler/healthcheck_test.go
similarity index 52%
copy from pitr/agent/internal/handler/handler_suite_test.go
copy to pitr/agent/internal/handler/healthcheck_test.go
index 816ef69..185ee18 100644
--- a/pitr/agent/internal/handler/handler_suite_test.go
+++ b/pitr/agent/internal/handler/healthcheck_test.go
@@ -18,43 +18,27 @@
 package handler_test
 
 import (
-	"os"
-	"testing"
+	"net/http"
+	"net/http/httptest"
+	"strings"
 
-	"github.com/apache/shardingsphere-on-cloud/pitr/agent/internal/handler"
-	"github.com/apache/shardingsphere-on-cloud/pitr/agent/pkg/logging"
-	"github.com/apache/shardingsphere-on-cloud/pitr/agent/pkg/responder"
-	"github.com/gofiber/fiber/v2"
-	"github.com/golang/mock/gomock"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
-	"go.uber.org/zap"
 )
 
-var (
-	app  *fiber.App
-	ctrl *gomock.Controller
-)
-
-func TestHandler(t *testing.T) {
-	RegisterFailHandler(Fail)
-	RunSpecs(t, "Handler Suite")
-}
-
-var _ = BeforeSuite(func() {
-	// init log
-	logging.Init(zap.DebugLevel)
-
-	Expect(os.Setenv("SHELL", "/bin/bash")).To(Succeed())
-
-	// init app
-	app = fiber.New()
-	app.Get("/ping", func(ctx *fiber.Ctx) error {
-		return responder.Success(ctx, "pong")
+var _ = Describe("Test Health Check", func() {
+	It("should failed with empty body", func() {
+		req := httptest.NewRequest(http.MethodPost, "/api/healthz", nil)
+		resp, err := app.Test(req)
+		Expect(err).To(BeNil())
+		Expect(resp.StatusCode).To(Equal(http.StatusInternalServerError))
 	})
 
-	app.Route("/api", func(r fiber.Router) {
-		r.Post("/diskspace", handler.DiskSpace)
-		r.Delete("/backup", handler.DeleteBackup)
+	It("should success", func() {
+		body := `{"username": "root", "password": "root", "db_name": "test", "db_port": 3306}`
+		req := httptest.NewRequest(http.MethodPost, "/api/healthz", strings.NewReader(body))
+		resp, err := app.Test(req)
+		Expect(err).To(BeNil())
+		Expect(resp.StatusCode).To(Equal(http.StatusInternalServerError))
 	})
 })
diff --git a/pitr/agent/internal/handler/view/healthcheck.go b/pitr/agent/internal/handler/view/healthcheck.go
new file mode 100644
index 0000000..f214fa0
--- /dev/null
+++ b/pitr/agent/internal/handler/view/healthcheck.go
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 view
+
+type HealthCheckIn struct {
+	DBPort   uint16 `json:"db_port"`
+	DBName   string `json:"db_name"`
+	Username string `json:"username"`
+	Password string `json:"password"`
+}
diff --git a/pitr/agent/main.go b/pitr/agent/main.go
index 8c11df7..a2bdddd 100644
--- a/pitr/agent/main.go
+++ b/pitr/agent/main.go
@@ -149,6 +149,7 @@ func SetupApp() {
 	)
 
 	app.Get("/ping", func(ctx *fiber.Ctx) error {
+
 		return responder.Success(ctx, "pong")
 	})
 
@@ -160,6 +161,7 @@ func SetupApp() {
 	app.Route("/api", func(r fiber.Router) {
 		r.Use(middleware.RequestIDChecker())
 
+		r.Post("/healthz", handler.HealthCheck)
 		r.Post("/backup", handler.Backup)
 		r.Delete("/backup", handler.DeleteBackup)
 		r.Post("/restore", handler.Restore)
diff --git a/pitr/agent/pkg/gsutil/conn.go b/pitr/agent/pkg/gsutil/conn.go
index 72a6e8a..a5bca1d 100644
--- a/pitr/agent/pkg/gsutil/conn.go
+++ b/pitr/agent/pkg/gsutil/conn.go
@@ -66,5 +66,5 @@ func (og *OpenGauss) Ping() error {
 		efmt := "db ping fail[user=%s,pwLen=%d,dbName=%s],err=%s,wrap=%w"
 		return fmt.Errorf(efmt, og.user, og.pwLen, og.dbName, err, cons.DBConnectionFailed)
 	}
-	return nil
+	return og.db.Close()
 }
diff --git a/pitr/cli/internal/cmd/backup.go b/pitr/cli/internal/cmd/backup.go
index 9ef1b63..8537501 100644
--- a/pitr/cli/internal/cmd/backup.go
+++ b/pitr/cli/internal/cmd/backup.go
@@ -143,6 +143,7 @@ func backup() error {
 	// Step3. Check agent server status
 	logging.Info("Checking agent server status...")
 	if available := checkAgentServerStatus(lsBackup); !available {
+		logging.Error("Cancel! One or more agent server are not available.")
 		err = xerr.NewCliErr("One or more agent server are not available.")
 		return err
 	}
@@ -179,6 +180,10 @@ func backup() error {
 	logging.Info("Starting check backup status ...")
 	status := checkBackupStatus(lsBackup)
 	logging.Info(fmt.Sprintf("Backup result: %s", status))
+	if status != model.SsBackupStatusCompleted && status != model.SsBackupStatusCanceled {
+		err = xerr.NewCliErr("Backup failed")
+		return err
+	}
 
 	// Step9. finished backup and update backup file
 	logging.Info("Starting update backup file ...")
@@ -437,7 +442,7 @@ func deleteBackupFiles(lsBackup *model.LsBackup) {
 		sn := sn
 		dn, ok := dataNodeMap[sn.IP]
 		if !ok {
-			logging.Error(fmt.Sprintf("data node %s:%d not found, SKIPPED!", sn.IP, sn.Port))
+			logging.Warn(fmt.Sprintf("SKIPPED! data node %s:%d not found in backup info.", sn.IP, sn.Port))
 			continue
 		}
 		as := pkg.NewAgentServer(fmt.Sprintf("%s:%d", convertLocalhost(sn.IP), AgentPort))
@@ -447,8 +452,12 @@ func deleteBackupFiles(lsBackup *model.LsBackup) {
 
 	time.Sleep(time.Millisecond * 100)
 	for pw.IsRenderInProgress() {
+		if pw.LengthActive() == 0 {
+			pw.Stop()
+		}
 		time.Sleep(time.Millisecond * 100)
 	}
+
 	close(resultCh)
 
 	t := table.NewWriter()
diff --git a/pitr/cli/internal/cmd/backup_test.go b/pitr/cli/internal/cmd/backup_test.go
index 6fbf352..bc531d6 100644
--- a/pitr/cli/internal/cmd/backup_test.go
+++ b/pitr/cli/internal/cmd/backup_test.go
@@ -383,18 +383,21 @@ var _ = Describe("test backup mock", func() {
 		})
 
 		It("agent server is not running", func() {
-			mockIreq.EXPECT().Send(gomock.Any()).Return(-1, errors.New("error")).AnyTimes()
+			mockIreq.EXPECT().Send(gomock.Any()).Return(errors.New("error")).AnyTimes()
+			mockIreq.EXPECT().Header(gomock.Any()).AnyTimes()
 			Expect(checkAgentServerStatus(ls)).To(BeFalse())
 		})
 
 		It("agent server are running", func() {
-			mockIreq.EXPECT().Send(gomock.Any()).Return(200, nil).AnyTimes()
+			mockIreq.EXPECT().Send(gomock.Any()).Return(nil).AnyTimes()
+			mockIreq.EXPECT().Header(gomock.Any()).AnyTimes()
 			Expect(checkAgentServerStatus(ls)).To(BeTrue())
 		})
 
 		It("one agent server is not running", func() {
-			mockIreq.EXPECT().Send(gomock.Any()).Return(500, nil)
-			mockIreq.EXPECT().Send(gomock.Any()).Return(200, nil).AnyTimes()
+			mockIreq.EXPECT().Send(gomock.Any()).Return(errors.New("failed"))
+			mockIreq.EXPECT().Send(gomock.Any()).Return(nil).AnyTimes()
+			mockIreq.EXPECT().Header(gomock.Any()).AnyTimes()
 			Expect(checkAgentServerStatus(ls)).To(BeFalse())
 		})
 	})
diff --git a/pitr/cli/internal/cmd/restore_test.go b/pitr/cli/internal/cmd/restore_test.go
index 04a12e1..7da6dea 100644
--- a/pitr/cli/internal/cmd/restore_test.go
+++ b/pitr/cli/internal/cmd/restore_test.go
@@ -126,7 +126,7 @@ var _ = Describe("test restore", func() {
 		RecordID = "backup-id"
 		proxy.EXPECT().ExportMetaData().Return(&model.ClusterInfo{}, nil)
 		proxy.EXPECT().ImportMetaData(gomock.Any()).Return(nil)
-		as.EXPECT().CheckStatus().Return(nil)
+		as.EXPECT().CheckStatus(gomock.Any()).Return(nil)
 		as.EXPECT().Restore(gomock.Any()).Return(nil)
 
 		Expect(restore()).To(BeNil())
diff --git a/pitr/cli/internal/cmd/root.go b/pitr/cli/internal/cmd/root.go
index 6df5fc7..33a4f40 100644
--- a/pitr/cli/internal/cmd/root.go
+++ b/pitr/cli/internal/cmd/root.go
@@ -93,10 +93,20 @@ func checkAgentServerStatus(lsBackup *model.LsBackup) bool {
 	// all agent server are available
 	available := true
 
+	// IMPORTANT: we don't support multiple agent server run on the same host
+	asMap := make(map[string]bool)
+	asDuplicate := false
+
 	for _, node := range lsBackup.SsBackup.StorageNodes {
 		sn := node
 		as := pkg.NewAgentServer(fmt.Sprintf("%s:%d", convertLocalhost(sn.IP), AgentPort))
-		if err := as.CheckStatus(); err != nil {
+		in := &model.HealthCheckIn{
+			DBPort:   sn.Port,
+			DBName:   convertLocalhost(sn.IP),
+			Username: sn.Username,
+			Password: sn.Password,
+		}
+		if err := as.CheckStatus(in); err != nil {
 			statusList = append(statusList, &model.AgentServerStatus{IP: sn.IP, Port: sn.Port, Status: "Unavailable"})
 			available = false
 		} else {
@@ -116,6 +126,19 @@ func checkAgentServerStatus(lsBackup *model.LsBackup) bool {
 
 	t.Render()
 
+	for _, node := range lsBackup.SsBackup.StorageNodes {
+		if _, ok := asMap[node.IP]; ok {
+			asDuplicate = true
+			break
+		}
+		asMap[node.IP] = true
+	}
+
+	if asDuplicate {
+		logging.Error("IMPORTANT!: we don't support multiple agent server run on the same host.\n")
+		return false
+	}
+
 	return available
 }
 
diff --git a/pitr/cli/internal/pkg/agent-server.go b/pitr/cli/internal/pkg/agent-server.go
index 9a35146..d6286a9 100644
--- a/pitr/cli/internal/pkg/agent-server.go
+++ b/pitr/cli/internal/pkg/agent-server.go
@@ -25,21 +25,21 @@ import (
 	"github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg/model"
 	"github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg/xerr"
 	"github.com/apache/shardingsphere-on-cloud/pitr/cli/pkg/httputils"
-	"github.com/google/uuid"
 )
 
 type agentServer struct {
 	addr string
 
-	_apiBackup     string
-	_apiRestore    string
-	_apiShowDetail string
-	_apiShowList   string
-	_apiDiskspace  string
+	_apiBackup      string
+	_apiRestore     string
+	_apiShowDetail  string
+	_apiShowList    string
+	_apiDiskspace   string
+	_apiHealthCheck string
 }
 
 type IAgentServer interface {
-	CheckStatus() error
+	CheckStatus(in *model.HealthCheckIn) error
 	Backup(in *model.BackupIn) (string, error)
 	DeleteBackup(in *model.DeleteBackupIn) error
 	Restore(in *model.RestoreIn) error
@@ -54,26 +54,25 @@ func NewAgentServer(addr string) IAgentServer {
 	return &agentServer{
 		addr: addr,
 
-		_apiBackup:     "/api/backup",
-		_apiRestore:    "/api/restore",
-		_apiShowDetail: "/api/show",
-		_apiShowList:   "/api/show/list",
-		_apiDiskspace:  "/api/diskspace",
+		_apiBackup:      "/api/backup",
+		_apiRestore:     "/api/restore",
+		_apiShowDetail:  "/api/show",
+		_apiShowList:    "/api/show/list",
+		_apiDiskspace:   "/api/diskspace",
+		_apiHealthCheck: "/api/healthz",
 	}
 }
 
 // CheckStatus check agent server is alive
-func (as *agentServer) CheckStatus() error {
-	url := fmt.Sprintf("%s/%s", as.addr, "ping")
-	r := httputils.NewRequest(context.Background(), http.MethodGet, url)
-	httpCode, err := r.Send(nil)
-	if err != nil {
-		efmt := "httputils.NewRequest[url=%s] return err=%s,wrap=%w"
-		return fmt.Errorf(efmt, url, err, xerr.NewCliErr(xerr.Unknown))
-	}
-	if httpCode != http.StatusOK {
-		return fmt.Errorf("httpCode=%d", httpCode)
+func (as *agentServer) CheckStatus(in *model.HealthCheckIn) error {
+	url := fmt.Sprintf("%s%s", as.addr, as._apiHealthCheck)
+
+	r := httputils.NewRequest(context.Background(), http.MethodPost, url)
+
+	if err := r.Send(in); err != nil {
+		return xerr.NewUnknownErr(url, in, nil, err)
 	}
+
 	return nil
 }
 
@@ -82,25 +81,14 @@ func (as *agentServer) Backup(in *model.BackupIn) (string, error) {
 
 	out := &model.BackupOutResp{}
 	r := httputils.NewRequest(context.Background(), http.MethodPost, url)
-	r.Header(map[string]string{
-		"x-request-id": uuid.New().String(),
-		"content-type": "application/json",
-	})
 	r.Body(in)
 
-	httpCode, err := r.Send(out)
-	if err != nil {
-		efmt := "httputils.NewRequest[url=%s,body=%v,out=%v] return err=%s,wrap=%w"
-		return "", fmt.Errorf(efmt, url, in, out, err, xerr.NewCliErr(xerr.Unknown))
-	}
-
-	if httpCode != http.StatusOK {
-		return "", fmt.Errorf("unknown http status[code=%d],err=%w", httpCode, xerr.NewCliErr(xerr.InvalidHTTPStatus))
+	if err := r.Send(out); err != nil {
+		return "", xerr.NewUnknownErr(url, in, out, err)
 	}
 
 	if out.Code != 0 {
-		asErr := xerr.NewAgentServerErr(out.Code, out.Msg)
-		return "", fmt.Errorf("agent server error[code=%d,msg=%s],err=%w", out.Code, out.Msg, asErr)
+		return "", xerr.NewAgentServerErr(out.Code, out.Msg)
 	}
 
 	return out.Data.ID, nil
@@ -112,24 +100,14 @@ func (as *agentServer) Restore(in *model.RestoreIn) error {
 
 	out := &model.RestoreResp{}
 	r := httputils.NewRequest(context.Background(), http.MethodPost, url)
-	r.Header(map[string]string{
-		"x-request-id": uuid.New().String(),
-		"content-type": "application/json",
-	})
 	r.Body(in)
-	httpCode, err := r.Send(out)
-	if err != nil {
-		efmt := "httputils.NewRequest[url=%s,body=%v,out=%v] return err=%s,wrap=%w"
-		return fmt.Errorf(efmt, url, in, out, err, xerr.NewCliErr(xerr.Unknown))
-	}
 
-	if httpCode != http.StatusOK {
-		return fmt.Errorf("unknown http status[code=%d],err=%w", httpCode, xerr.NewCliErr(xerr.InvalidHTTPStatus))
+	if err := r.Send(out); err != nil {
+		return xerr.NewUnknownErr(url, in, out, err)
 	}
 
 	if out.Code != 0 {
-		asErr := xerr.NewAgentServerErr(out.Code, out.Msg)
-		return fmt.Errorf("agent server error[code=%d,msg=%s],err=%w", out.Code, out.Msg, asErr)
+		return xerr.NewAgentServerErr(out.Code, out.Msg)
 	}
 
 	return nil
@@ -140,24 +118,14 @@ func (as *agentServer) ShowDetail(in *model.ShowDetailIn) (*model.BackupInfo, er
 
 	out := &model.BackupDetailResp{}
 	r := httputils.NewRequest(context.Background(), http.MethodPost, url)
-	r.Header(map[string]string{
-		"x-request-id": uuid.New().String(),
-		"content-type": "application/json",
-	})
 	r.Body(in)
-	httpCode, err := r.Send(out)
-	if err != nil {
-		efmt := "httputils.NewRequest[url=%s,body=%v,out=%v] return err=%s,wrap=%w"
-		return nil, fmt.Errorf(efmt, url, in, out, err, xerr.NewCliErr(xerr.Unknown))
-	}
 
-	if httpCode != http.StatusOK {
-		return nil, fmt.Errorf("unknown http status[code=%d],err=%w", httpCode, xerr.NewCliErr(xerr.InvalidHTTPStatus))
+	if err := r.Send(out); err != nil {
+		return nil, xerr.NewUnknownErr(url, in, out, err)
 	}
 
 	if out.Code != 0 {
-		asErr := xerr.NewAgentServerErr(out.Code, out.Msg)
-		return nil, fmt.Errorf("agent server error[code=%d,msg=%s],err=%w", out.Code, out.Msg, asErr)
+		return nil, xerr.NewAgentServerErr(out.Code, out.Msg)
 	}
 
 	return &out.Data, nil
@@ -168,24 +136,14 @@ func (as *agentServer) ShowList(in *model.ShowListIn) ([]model.BackupInfo, error
 
 	out := &model.BackupListResp{}
 	r := httputils.NewRequest(context.Background(), http.MethodPost, url)
-	r.Header(map[string]string{
-		"x-request-id": uuid.New().String(),
-		"content-type": "application/json",
-	})
 	r.Body(in)
-	httpCode, err := r.Send(out)
-	if err != nil {
-		efmt := "httputils.NewRequest[url=%s,body=%v,out=%v] return err=%s,wrap=%w"
-		return nil, fmt.Errorf(efmt, url, in, out, err, xerr.NewCliErr(xerr.Unknown))
-	}
 
-	if httpCode != http.StatusOK {
-		return nil, fmt.Errorf("unknown http status[code=%d],err=%w", httpCode, xerr.NewCliErr(xerr.InvalidHTTPStatus))
+	if err := r.Send(out); err != nil {
+		return nil, xerr.NewUnknownErr(url, in, out, err)
 	}
 
 	if out.Code != 0 {
-		asErr := xerr.NewAgentServerErr(out.Code, out.Msg)
-		return nil, fmt.Errorf("agent server error[code=%d,msg=%s],err=%w", out.Code, out.Msg, asErr)
+		return nil, xerr.NewAgentServerErr(out.Code, out.Msg)
 	}
 
 	return out.Data, nil
@@ -196,25 +154,14 @@ func (as *agentServer) ShowDiskSpace(in *model.DiskSpaceIn) (*model.DiskSpaceInf
 
 	out := &model.DiskSpaceInfo{}
 	r := httputils.NewRequest(context.Background(), http.MethodPost, url)
-	r.Header(map[string]string{
-		"x-request-id": uuid.New().String(),
-		"content-type": "application/json",
-	})
 	r.Body(in)
 
-	httpCode, err := r.Send(out)
-	if err != nil {
-		efmt := "httputils.NewRequest[url=%s,body=%v,out=%v] return err=%s,wrap=%w"
-		return nil, fmt.Errorf(efmt, url, in, out, err, xerr.NewCliErr(xerr.Unknown))
-	}
-
-	if httpCode != http.StatusOK {
-		return nil, fmt.Errorf("unknown http status[code=%d],err=%w", httpCode, xerr.NewCliErr(xerr.InvalidHTTPStatus))
+	if err := r.Send(out); err != nil {
+		return nil, xerr.NewUnknownErr(url, in, out, err)
 	}
 
 	if out.Code != 0 {
-		asErr := xerr.NewAgentServerErr(out.Code, out.Msg)
-		return nil, fmt.Errorf("agent server error[code=%d,msg=%s],err=%w", out.Code, out.Msg, asErr)
+		return nil, xerr.NewAgentServerErr(out.Code, out.Msg)
 	}
 
 	return out, nil
@@ -226,25 +173,14 @@ func (as *agentServer) DeleteBackup(in *model.DeleteBackupIn) error {
 
 	out := &model.DeleteBackupOut{}
 	r := httputils.NewRequest(context.Background(), http.MethodDelete, url)
-	r.Header(map[string]string{
-		"x-request-id": uuid.New().String(),
-		"content-type": "application/json",
-	})
 	r.Body(in)
 
-	httpCode, err := r.Send(out)
-	if err != nil {
-		efmt := "httputils.NewRequest[url=%s,body=%v,out=%v] return err=%s,wrap=%w"
-		return fmt.Errorf(efmt, url, in, out, err, xerr.NewCliErr(xerr.Unknown))
-	}
-
-	if httpCode != http.StatusOK {
-		return fmt.Errorf("unknown http status[code=%d],err=%w", httpCode, xerr.NewCliErr(xerr.InvalidHTTPStatus))
+	if err := r.Send(out); err != nil {
+		return xerr.NewUnknownErr(url, in, out, err)
 	}
 
 	if out.Code != 0 {
-		asErr := xerr.NewAgentServerErr(out.Code, out.Msg)
-		return fmt.Errorf("agent server error[code=%d,msg=%s],err=%w", out.Code, out.Msg, asErr)
+		return xerr.NewAgentServerErr(out.Code, out.Msg)
 	}
 
 	return nil
diff --git a/pitr/cli/internal/pkg/agent-server_test.go b/pitr/cli/internal/pkg/agent-server_test.go
index 1bf05f2..e76b8f3 100644
--- a/pitr/cli/internal/pkg/agent-server_test.go
+++ b/pitr/cli/internal/pkg/agent-server_test.go
@@ -132,7 +132,6 @@ var _ = Describe("AgentServer", func() {
 		as = NewAgentServer("http://agent-server:18080")
 		ctrl = gomock.NewController(GinkgoT())
 		req = mock_httputils.NewMockIreq(ctrl)
-		req.EXPECT().Header(gomock.Any())
 		req.EXPECT().Body(gomock.Any())
 		monkey.Patch(httputils.NewRequest, func(c context.Context, method, url string) httputils.Ireq {
 			return req
@@ -145,7 +144,7 @@ var _ = Describe("AgentServer", func() {
 
 	Context("backup", func() {
 		It("backup failed", func() {
-			req.EXPECT().Send(gomock.Any()).Return(-1, fmt.Errorf("error"))
+			req.EXPECT().Send(gomock.Any()).Return(fmt.Errorf("error"))
 			_, err := as.Backup(&model.BackupIn{})
 			Expect(err).ShouldNot(BeNil())
 		})
@@ -153,7 +152,7 @@ var _ = Describe("AgentServer", func() {
 		It("backup success", func() {
 			req.EXPECT().Send(gomock.Any()).Do(func(i *model.BackupOutResp) {
 				i.Data.ID = "backup-id"
-			}).Return(200, nil)
+			}).Return(nil)
 			as := NewAgentServer("http://agent-server:18080")
 			resp, err := as.Backup(&model.BackupIn{})
 			Expect(err).Should(BeNil())
@@ -163,13 +162,13 @@ var _ = Describe("AgentServer", func() {
 
 	Context("restore", func() {
 		It("restore failed", func() {
-			req.EXPECT().Send(gomock.Any()).Return(-1, fmt.Errorf("error"))
+			req.EXPECT().Send(gomock.Any()).Return(fmt.Errorf("error"))
 			err := as.Restore(&model.RestoreIn{})
 			Expect(err).ShouldNot(BeNil())
 		})
 		// restore success
 		It("restore success", func() {
-			req.EXPECT().Send(gomock.Any()).Return(200, nil)
+			req.EXPECT().Send(gomock.Any()).Return(nil)
 			err := as.Restore(&model.RestoreIn{})
 			Expect(err).Should(BeNil())
 		})
@@ -178,7 +177,7 @@ var _ = Describe("AgentServer", func() {
 
 	Context("show detail", func() {
 		It("show detail failed", func() {
-			req.EXPECT().Send(gomock.Any()).Return(-1, fmt.Errorf("error"))
+			req.EXPECT().Send(gomock.Any()).Return(fmt.Errorf("error"))
 			_, err := as.ShowDetail(&model.ShowDetailIn{})
 			Expect(err).ShouldNot(BeNil())
 		})
@@ -186,7 +185,7 @@ var _ = Describe("AgentServer", func() {
 		It("show detail success", func() {
 			req.EXPECT().Send(gomock.Any()).Do(func(i *model.BackupDetailResp) {
 				i.Data = model.BackupInfo{}
-			}).Return(200, nil)
+			}).Return(nil)
 			resp, err := as.ShowDetail(&model.ShowDetailIn{})
 			Expect(err).Should(BeNil())
 			Expect(resp).Should(Equal(&model.BackupInfo{}))
@@ -195,7 +194,7 @@ var _ = Describe("AgentServer", func() {
 
 	Context("show list", func() {
 		It("show list failed", func() {
-			req.EXPECT().Send(gomock.Any()).Return(-1, fmt.Errorf("error"))
+			req.EXPECT().Send(gomock.Any()).Return(fmt.Errorf("error"))
 			_, err := as.ShowList(&model.ShowListIn{})
 			Expect(err).ShouldNot(BeNil())
 		})
@@ -203,7 +202,7 @@ var _ = Describe("AgentServer", func() {
 		It("show list success", func() {
 			req.EXPECT().Send(gomock.Any()).Do(func(i *model.BackupListResp) {
 				i.Data = []model.BackupInfo{}
-			}).Return(200, nil)
+			}).Return(nil)
 			resp, err := as.ShowList(&model.ShowListIn{})
 			Expect(err).Should(BeNil())
 			Expect(resp).Should(Equal([]model.BackupInfo{}))
diff --git a/pitr/cli/internal/pkg/mocks/agent-server.go b/pitr/cli/internal/pkg/mocks/agent-server.go
index e736033..c9047d8 100644
--- a/pitr/cli/internal/pkg/mocks/agent-server.go
+++ b/pitr/cli/internal/pkg/mocks/agent-server.go
@@ -50,17 +50,17 @@ func (mr *MockIAgentServerMockRecorder) Backup(in interface{}) *gomock.Call {
 }
 
 // CheckStatus mocks base method.
-func (m *MockIAgentServer) CheckStatus() error {
+func (m *MockIAgentServer) CheckStatus(in *model.HealthCheckIn) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "CheckStatus")
+	ret := m.ctrl.Call(m, "CheckStatus", in)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // CheckStatus indicates an expected call of CheckStatus.
-func (mr *MockIAgentServerMockRecorder) CheckStatus() *gomock.Call {
+func (mr *MockIAgentServerMockRecorder) CheckStatus(in interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckStatus", reflect.TypeOf((*MockIAgentServer)(nil).CheckStatus))
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckStatus", reflect.TypeOf((*MockIAgentServer)(nil).CheckStatus), in)
 }
 
 // DeleteBackup mocks base method.
diff --git a/pitr/cli/internal/pkg/model/as_healthcheck.go b/pitr/cli/internal/pkg/model/as_healthcheck.go
new file mode 100644
index 0000000..4dae579
--- /dev/null
+++ b/pitr/cli/internal/pkg/model/as_healthcheck.go
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 model
+
+type HealthCheckIn struct {
+	DBPort   uint16 `json:"db_port"`
+	DBName   string `json:"db_name"`
+	Username string `json:"username"`
+	Password string `json:"password"`
+}
diff --git a/pitr/cli/internal/pkg/xerr/err.go b/pitr/cli/internal/pkg/xerr/err.go
index 5e363d6..66f8dd3 100644
--- a/pitr/cli/internal/pkg/xerr/err.go
+++ b/pitr/cli/internal/pkg/xerr/err.go
@@ -24,6 +24,8 @@ type err struct {
 }
 
 const (
+	postErrFmt = "httputils.NewRequest[url=%s,body=%v,out=%v] return err=%s,wrap=%w"
+
 	Unknown           = "Unknown error"
 	InvalidHTTPStatus = "Invalid http status"
 	NotFound          = "Not found"
@@ -39,6 +41,10 @@ func NewCliErr(msg string) error {
 	}
 }
 
+func NewUnknownErr(url string, in, out interface{}, err error) error {
+	return fmt.Errorf(postErrFmt, url, in, out, err, NewCliErr(Unknown))
+}
+
 func NewAgentServerErr(code int, msg string) error {
 	return &err{
 		msg: fmt.Sprintf("agent server err[code=%d,msg=%s]", code, msg),
diff --git a/pitr/cli/pkg/httputils/mocks/req.go b/pitr/cli/pkg/httputils/mocks/req.go
index 0cfe247..c7bc91a 100644
--- a/pitr/cli/pkg/httputils/mocks/req.go
+++ b/pitr/cli/pkg/httputils/mocks/req.go
@@ -1,22 +1,5 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one or more
-* contributor license agreements.  See the NOTICE file distributed with
-* this work for additional information regarding copyright ownership.
-* The ASF licenses this file to You 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.
- */
-
 // Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/httputils/req.go
+// Source: req.go
 
 // Package mock_httputils is a generated GoMock package.
 package mock_httputils
@@ -87,12 +70,11 @@ func (mr *MockIreqMockRecorder) Query(m interface{}) *gomock.Call {
 }
 
 // Send mocks base method.
-func (m *MockIreq) Send(body any) (int, error) {
+func (m *MockIreq) Send(body any) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Send", body)
-	ret0, _ := ret[0].(int)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
+	ret0, _ := ret[0].(error)
+	return ret0
 }
 
 // Send indicates an expected call of Send.
diff --git a/pitr/cli/pkg/httputils/req.go b/pitr/cli/pkg/httputils/req.go
index 7d99079..61ae871 100644
--- a/pitr/cli/pkg/httputils/req.go
+++ b/pitr/cli/pkg/httputils/req.go
@@ -26,6 +26,8 @@ import (
 	"io"
 	"net/http"
 	"strings"
+
+	"github.com/google/uuid"
 )
 
 type req struct {
@@ -41,7 +43,7 @@ type Ireq interface {
 	Header(h map[string]string)
 	Body(b any)
 	Query(m map[string]string)
-	Send(body any) (int, error)
+	Send(body any) error
 }
 
 func NewRequest(ctx context.Context, method, url string) Ireq {
@@ -68,7 +70,7 @@ func (r *req) Query(m map[string]string) {
 	r.query = m
 }
 
-func (r *req) Send(body any) (int, error) {
+func (r *req) Send(body any) error {
 	var (
 		bs  []byte
 		err error
@@ -77,19 +79,29 @@ func (r *req) Send(body any) (int, error) {
 	if r.body != nil {
 		bs, err = json.Marshal(r.body)
 		if err != nil {
-			return -1, fmt.Errorf("json.Marshal return err=%w", err)
+			return fmt.Errorf("json.Marshal return err=%w", err)
 		}
 	}
 
 	_req, err := http.NewRequestWithContext(r.ctx, r.method, r.url, bytes.NewReader(bs))
 	if err != nil {
-		return -1, fmt.Errorf("new request failure,err=%w", err)
+		return fmt.Errorf("new request failure,err=%w", err)
 	}
 
 	for k, v := range r.header {
 		_req.Header.Set(k, v)
 	}
 
+	// set default header
+	if r.method == http.MethodPost {
+		if _req.Header.Get("Content-Type") == "" {
+			_req.Header.Set("Content-Type", "application/json")
+		}
+		if _req.Header.Get("x-request-id") == "" {
+			_req.Header.Set("x-request-id", uuid.New().String())
+		}
+	}
+
 	for k, v := range r.query {
 		values := _req.URL.Query()
 		values.Add(k, v)
@@ -103,20 +115,24 @@ func (r *req) Send(body any) (int, error) {
 	c := &http.Client{Transport: tr}
 	resp, err := c.Do(_req)
 	if err != nil {
-		return -1, fmt.Errorf("http request err=%w", err)
+		return fmt.Errorf("http request err=%w", err)
 	}
 
 	defer resp.Body.Close()
 
 	all, err := io.ReadAll(resp.Body)
 	if err != nil {
-		return -1, fmt.Errorf("invalid response,err=%w", err)
+		return fmt.Errorf("invalid response,err=%w", err)
 	}
 	if body != nil {
 		if err = json.Unmarshal(all, body); err != nil {
-			return -1, fmt.Errorf("json unmarshal return err=%w", err)
+			return fmt.Errorf("json unmarshal return err=%w", err)
 		}
 	}
 
-	return resp.StatusCode, nil
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("response status code is not 200, code=%d", resp.StatusCode)
+	}
+
+	return nil
 }
diff --git a/shardingsphere-operator/pkg/kubernetes/deployment/deployment_test.go b/shardingsphere-operator/pkg/kubernetes/deployment/deployment_test.go
index 2267ff0..d1270cd 100644
--- a/shardingsphere-operator/pkg/kubernetes/deployment/deployment_test.go
+++ b/shardingsphere-operator/pkg/kubernetes/deployment/deployment_test.go
@@ -273,7 +273,7 @@ func Test_NewDeployment(t *testing.T) {
 						"k1": "v1",
 					},
 					Annotations: map[string]string{
-						"anno1": "value1",
+						defaultAnnotationJavaAgentEnabled: "true",
 					},
 				},
 				Spec: appsv1.DeploymentSpec{