Skip to content

Commit

Permalink
Modify health check endpoint and add HEAD method (#958)
Browse files Browse the repository at this point in the history
Change health check endpoint to include yorkie service endpoint along with HEAD method to support various health checkers.
  • Loading branch information
taeng0204 authored Aug 15, 2024
1 parent 80c6ea0 commit ad23c56
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 27 deletions.
23 changes: 10 additions & 13 deletions server/rpc/httphealth/httphealth.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import (
"connectrpc.com/grpchealth"
)

// serviceName is the path for the health check endpoint.
const serviceName = "/healthz/"
// HealthV1ServiceName is the fully-qualified name of the v1 version of the health service.
const HealthV1ServiceName = "/yorkie.v1.YorkieService/health"

// CheckResponse represents the response structure for health checks.
type CheckResponse struct {
Expand All @@ -35,35 +35,32 @@ type CheckResponse struct {
// NewHandler creates a new HTTP handler for health checks.
func NewHandler(checker grpchealth.Checker) (string, http.Handler) {
check := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
if r.Method != http.MethodGet && r.Method != http.MethodHead {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

var checkRequest grpchealth.CheckRequest
if service := r.URL.Query().Get("service"); service != "" {
service := r.URL.Query().Get("service")
if service != "" {
checkRequest.Service = service
}

checkResponse, err := checker.Check(r.Context(), &checkRequest)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}

resp, err := json.Marshal(CheckResponse{checkResponse.Status.String()})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)

if _, err := w.Write(resp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
if r.Method == http.MethodGet {
if _, err := w.Write(resp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
})

return serviceName, check
return HealthV1ServiceName, check
}
63 changes: 49 additions & 14 deletions test/integration/health_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ import (

"connectrpc.com/grpchealth"
"github.com/stretchr/testify/assert"
"github.com/yorkie-team/yorkie/api/yorkie/v1/v1connect"
"github.com/yorkie-team/yorkie/server/rpc/httphealth"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
healthpb "google.golang.org/grpc/health/grpc_health_v1"

"github.com/yorkie-team/yorkie/api/yorkie/v1/v1connect"
"github.com/yorkie-team/yorkie/server/rpc/httphealth"
)

var services = []string{
Expand All @@ -55,7 +54,7 @@ func TestRPCHealthCheck(t *testing.T) {
t.Run("Service: default", func(t *testing.T) {
resp, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{})
assert.NoError(t, err)
assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING)
assert.Equal(t, healthpb.HealthCheckResponse_SERVING, resp.Status)
})

// check all services
Expand All @@ -66,7 +65,7 @@ func TestRPCHealthCheck(t *testing.T) {
Service: service,
})
assert.NoError(t, err)
assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING)
assert.Equal(t, healthpb.HealthCheckResponse_SERVING, resp.Status)
})
}

Expand All @@ -79,48 +78,84 @@ func TestRPCHealthCheck(t *testing.T) {
})
}

func TestHTTPHealthCheck(t *testing.T) {
func TestHTTPGETHealthCheck(t *testing.T) {
// check default service
t.Run("Service: default", func(t *testing.T) {
resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/")
resp, err := http.Get("http://" + defaultServer.RPCAddr() + httphealth.HealthV1ServiceName)
assert.NoError(t, err)
defer func() {
assert.NoError(t, resp.Body.Close())
}()
assert.Equal(t, resp.StatusCode, http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

var healthResp httphealth.CheckResponse
err = json.NewDecoder(resp.Body).Decode(&healthResp)
assert.NoError(t, err)
assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String())
assert.Equal(t, grpchealth.StatusServing.String(), healthResp.Status)
})

// check all services
for _, s := range services {
service := s
t.Run("Service: "+service, func(t *testing.T) {
url := "http://" + defaultServer.RPCAddr() + "/healthz/?service=" + service
url := "http://" + defaultServer.RPCAddr() + httphealth.HealthV1ServiceName + "?service=" + service
resp, err := http.Get(url)
assert.NoError(t, err)
defer func() {
assert.NoError(t, resp.Body.Close())
}()
assert.Equal(t, resp.StatusCode, http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

var healthResp httphealth.CheckResponse
err = json.NewDecoder(resp.Body).Decode(&healthResp)
assert.NoError(t, err)
assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String())
assert.Equal(t, grpchealth.StatusServing.String(), healthResp.Status)
})
}

// check unknown service
t.Run("Service: unknown", func(t *testing.T) {
resp, err := http.Get("http://" + defaultServer.RPCAddr() + httphealth.HealthV1ServiceName + "?service=unknown")
assert.NoError(t, err)
defer func() {
assert.NoError(t, resp.Body.Close())
}()
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
})
}

func TestHTTPHEADHealthCheck(t *testing.T) {
// check default service
t.Run("Service: default", func(t *testing.T) {
resp, err := http.Head("http://" + defaultServer.RPCAddr() + httphealth.HealthV1ServiceName)
assert.NoError(t, err)
defer func() {
assert.NoError(t, resp.Body.Close())
}()
assert.Equal(t, http.StatusOK, resp.StatusCode)
})

// check all services
for _, s := range services {
service := s
t.Run("Service: "+service, func(t *testing.T) {
url := "http://" + defaultServer.RPCAddr() + httphealth.HealthV1ServiceName + "?service=" + service
resp, err := http.Head(url)
assert.NoError(t, err)
defer func() {
assert.NoError(t, resp.Body.Close())
}()
assert.Equal(t, http.StatusOK, resp.StatusCode)
})
}

// check unknown service
t.Run("Service: unknown", func(t *testing.T) {
resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=unknown")
resp, err := http.Head("http://" + defaultServer.RPCAddr() + httphealth.HealthV1ServiceName + "?service=unknown")
assert.NoError(t, err)
defer func() {
assert.NoError(t, resp.Body.Close())
}()
assert.Equal(t, resp.StatusCode, http.StatusNotFound)
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
})
}

0 comments on commit ad23c56

Please sign in to comment.