From 7ebe5f446348a2dd5876246ddc032416a4cb6bfc Mon Sep 17 00:00:00 2001 From: taeng0204 Date: Sun, 4 Aug 2024 21:40:04 +0900 Subject: [PATCH 01/12] Add HTTP health check handler for server health monitoring (#832) Added a handler to allow health checks to be performed with plain HTTP GET requests, rather than health checks using rpc. --- server/rpc/health/health.go | 62 +++++++++++++++++++++++++++++++++ server/rpc/server.go | 15 ++++---- test/integration/health_test.go | 21 ++++++++++- 3 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 server/rpc/health/health.go diff --git a/server/rpc/health/health.go b/server/rpc/health/health.go new file mode 100644 index 000000000..9835a2869 --- /dev/null +++ b/server/rpc/health/health.go @@ -0,0 +1,62 @@ +/* + * Copyright 2024 The Yorkie Authors. All rights reserved. + * + * 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 health uses http GET to provide a health check for the server. +package health + +import ( + "encoding/json" + "net/http" + + "connectrpc.com/grpchealth" +) + +// HealthCheckResponse represents the response structure for health checks. +type HealthCheckResponse struct { + Status string `json:"status"` +} + +// NewHTTPHealthCheckHandler creates a new HTTP handler for health checks. +func NewHTTPHealthCheckHandler(checker grpchealth.Checker) (string, http.Handler) { + const serviceName = "/healthz/" + check := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + var checkRequest grpchealth.CheckRequest + 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(HealthCheckResponse{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) + } + }) + return serviceName, check +} diff --git a/server/rpc/server.go b/server/rpc/server.go index 85f57b521..bd4acdb72 100644 --- a/server/rpc/server.go +++ b/server/rpc/server.go @@ -36,6 +36,7 @@ import ( "github.com/yorkie-team/yorkie/server/backend" "github.com/yorkie-team/yorkie/server/logging" "github.com/yorkie-team/yorkie/server/rpc/auth" + "github.com/yorkie-team/yorkie/server/rpc/health" "github.com/yorkie-team/yorkie/server/rpc/interceptors" ) @@ -62,16 +63,18 @@ func NewServer(conf *Config, be *backend.Backend) (*Server, error) { ), } - yorkieServiceCtx, yorkieServiceCancel := context.WithCancel(context.Background()) - mux := http.NewServeMux() - mux.Handle(v1connect.NewYorkieServiceHandler(newYorkieServer(yorkieServiceCtx, be), opts...)) - mux.Handle(v1connect.NewAdminServiceHandler(newAdminServer(be, tokenManager), opts...)) - mux.Handle(grpchealth.NewHandler(grpchealth.NewStaticChecker( + healthChecker := grpchealth.NewStaticChecker( grpchealth.HealthV1ServiceName, v1connect.YorkieServiceName, v1connect.AdminServiceName, - ))) + ) + yorkieServiceCtx, yorkieServiceCancel := context.WithCancel(context.Background()) + mux := http.NewServeMux() + mux.Handle(v1connect.NewYorkieServiceHandler(newYorkieServer(yorkieServiceCtx, be), opts...)) + mux.Handle(v1connect.NewAdminServiceHandler(newAdminServer(be, tokenManager), opts...)) + mux.Handle(grpchealth.NewHandler(healthChecker)) + mux.Handle(health.NewHTTPHealthCheckHandler(healthChecker)) // TODO(hackerwins): We need to provide proper http server configuration. return &Server{ conf: conf, diff --git a/test/integration/health_test.go b/test/integration/health_test.go index 6ee176de5..52e66bb6b 100644 --- a/test/integration/health_test.go +++ b/test/integration/health_test.go @@ -20,15 +20,19 @@ package integration import ( "context" + "encoding/json" + "net/http" "testing" + "connectrpc.com/grpchealth" "github.com/stretchr/testify/assert" + "github.com/yorkie-team/yorkie/server/rpc/health" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" healthpb "google.golang.org/grpc/health/grpc_health_v1" ) -func TestHealthCheck(t *testing.T) { +func TestRPCHealthCheck(t *testing.T) { // use gRPC health check conn, err := grpc.Dial( defaultServer.RPCAddr(), @@ -44,3 +48,18 @@ func TestHealthCheck(t *testing.T) { assert.NoError(t, err) assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) } + +func TestHTTPHealthCheck(t *testing.T) { + // use HTTP health check + resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/") + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.NoError(t, err) + assert.Equal(t, resp.StatusCode, 200) + + var healthResp health.HealthCheckResponse + err = json.NewDecoder(resp.Body).Decode(&healthResp) + assert.NoError(t, err) + assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) +} From e615224fbd7bc083f2dac23bbb1164c0052588a9 Mon Sep 17 00:00:00 2001 From: taeng0204 Date: Mon, 5 Aug 2024 11:58:09 +0900 Subject: [PATCH 02/12] health check response struct name change (#832) Rename existing names used by other packages for lint --- server/rpc/health/health.go | 6 +++--- test/integration/health_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/rpc/health/health.go b/server/rpc/health/health.go index 9835a2869..5d4dd043a 100644 --- a/server/rpc/health/health.go +++ b/server/rpc/health/health.go @@ -24,8 +24,8 @@ import ( "connectrpc.com/grpchealth" ) -// HealthCheckResponse represents the response structure for health checks. -type HealthCheckResponse struct { +// CheckResponse represents the response structure for health checks. +type CheckResponse struct { Status string `json:"status"` } @@ -47,7 +47,7 @@ func NewHTTPHealthCheckHandler(checker grpchealth.Checker) (string, http.Handler http.Error(w, err.Error(), http.StatusNotFound) return } - resp, err := json.Marshal(HealthCheckResponse{checkResponse.Status.String()}) + resp, err := json.Marshal(CheckResponse{checkResponse.Status.String()}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/test/integration/health_test.go b/test/integration/health_test.go index 52e66bb6b..1ab33df2a 100644 --- a/test/integration/health_test.go +++ b/test/integration/health_test.go @@ -58,7 +58,7 @@ func TestHTTPHealthCheck(t *testing.T) { assert.NoError(t, err) assert.Equal(t, resp.StatusCode, 200) - var healthResp health.HealthCheckResponse + var healthResp health.CheckResponse err = json.NewDecoder(resp.Body).Decode(&healthResp) assert.NoError(t, err) assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) From 1f9da2d42bf35ca65a30019f9dbd9d2f8faa79ef Mon Sep 17 00:00:00 2001 From: taeng0204 Date: Mon, 5 Aug 2024 16:48:28 +0900 Subject: [PATCH 03/12] Rename handler functions (#832) The package name includes health, so we remove health from the handler function --- server/rpc/health/health.go | 4 ++-- server/rpc/server.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/rpc/health/health.go b/server/rpc/health/health.go index 5d4dd043a..0eb54c116 100644 --- a/server/rpc/health/health.go +++ b/server/rpc/health/health.go @@ -29,8 +29,8 @@ type CheckResponse struct { Status string `json:"status"` } -// NewHTTPHealthCheckHandler creates a new HTTP handler for health checks. -func NewHTTPHealthCheckHandler(checker grpchealth.Checker) (string, http.Handler) { +// NewHTTPHandler creates a new HTTP handler for health checks. +func NewHTTPHandler(checker grpchealth.Checker) (string, http.Handler) { const serviceName = "/healthz/" check := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { diff --git a/server/rpc/server.go b/server/rpc/server.go index bd4acdb72..ded0cdb8c 100644 --- a/server/rpc/server.go +++ b/server/rpc/server.go @@ -74,7 +74,7 @@ func NewServer(conf *Config, be *backend.Backend) (*Server, error) { mux.Handle(v1connect.NewYorkieServiceHandler(newYorkieServer(yorkieServiceCtx, be), opts...)) mux.Handle(v1connect.NewAdminServiceHandler(newAdminServer(be, tokenManager), opts...)) mux.Handle(grpchealth.NewHandler(healthChecker)) - mux.Handle(health.NewHTTPHealthCheckHandler(healthChecker)) + mux.Handle(health.NewHTTPHandler(healthChecker)) // TODO(hackerwins): We need to provide proper http server configuration. return &Server{ conf: conf, From 0bfba1f0d602054161a84f22a3840b6fa82af067 Mon Sep 17 00:00:00 2001 From: taeng0204 Date: Tue, 6 Aug 2024 12:28:45 +0900 Subject: [PATCH 04/12] Add a service-specific health check test Write a health check for each service and a check failure test for the unknown service. --- test/integration/health_test.go | 131 ++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/test/integration/health_test.go b/test/integration/health_test.go index 1ab33df2a..4e7e7265f 100644 --- a/test/integration/health_test.go +++ b/test/integration/health_test.go @@ -26,6 +26,7 @@ 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/health" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -49,6 +50,81 @@ func TestRPCHealthCheck(t *testing.T) { assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) } +func TestRPCHealthCheckYorkie(t *testing.T) { + // use gRPC health check for Yorkie + conn, err := grpc.Dial( + defaultServer.RPCAddr(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + assert.NoError(t, err) + defer func() { + assert.NoError(t, conn.Close()) + }() + + cli := healthpb.NewHealthClient(conn) + resp, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{ + Service: v1connect.YorkieServiceName, + }) + assert.NoError(t, err) + assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) +} + +func TestRPCHealthCheckAdmin(t *testing.T) { + // use gRPC health check for Admin + conn, err := grpc.Dial( + defaultServer.RPCAddr(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + assert.NoError(t, err) + defer func() { + assert.NoError(t, conn.Close()) + }() + + cli := healthpb.NewHealthClient(conn) + resp, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{ + Service: v1connect.AdminServiceName, + }) + assert.NoError(t, err) + assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) +} + +func TestRPCHealthCheckHealthService(t *testing.T) { + // use gRPC health check for health service + conn, err := grpc.Dial( + defaultServer.RPCAddr(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + assert.NoError(t, err) + defer func() { + assert.NoError(t, conn.Close()) + }() + + cli := healthpb.NewHealthClient(conn) + resp, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{ + Service: grpchealth.HealthV1ServiceName, + }) + assert.NoError(t, err) + assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) +} + +func TestRPCHealthCheckUnknownService(t *testing.T) { + // use gRPC health check for unknown service + conn, err := grpc.Dial( + defaultServer.RPCAddr(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + assert.NoError(t, err) + defer func() { + assert.NoError(t, conn.Close()) + }() + + cli := healthpb.NewHealthClient(conn) + _, err = cli.Check(context.Background(), &healthpb.HealthCheckRequest{ + Service: "unknown", + }) + assert.Error(t, err) +} + func TestHTTPHealthCheck(t *testing.T) { // use HTTP health check resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/") @@ -63,3 +139,58 @@ func TestHTTPHealthCheck(t *testing.T) { assert.NoError(t, err) assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) } + +func TestHTTPHealthCheckYorkie(t *testing.T) { + // use HTTP health check for Yorkie + resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=" + v1connect.YorkieServiceName) + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.NoError(t, err) + assert.Equal(t, resp.StatusCode, 200) + + var healthResp health.CheckResponse + err = json.NewDecoder(resp.Body).Decode(&healthResp) + assert.NoError(t, err) + assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) +} + +func TestHTTPHealthCheckAdmin(t *testing.T) { + // use HTTP health check for Admin + resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=" + v1connect.AdminServiceName) + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.NoError(t, err) + assert.Equal(t, resp.StatusCode, 200) + + var healthResp health.CheckResponse + err = json.NewDecoder(resp.Body).Decode(&healthResp) + assert.NoError(t, err) + assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) +} + +func TestHTTPHealthCheckHealthService(t *testing.T) { + // use HTTP health check for health service + resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=" + grpchealth.HealthV1ServiceName) + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.NoError(t, err) + assert.Equal(t, resp.StatusCode, 200) + + var healthResp health.CheckResponse + err = json.NewDecoder(resp.Body).Decode(&healthResp) + assert.NoError(t, err) + assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) +} + +func TestHTTPHealthCheckUnknownService(t *testing.T) { + // use HTTP health check for unknown service + resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=unknown") + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.NoError(t, err) + assert.Equal(t, resp.StatusCode, 404) +} From 1f596d70bdd9a8c1c7394a6fa10a18e14d8c4e92 Mon Sep 17 00:00:00 2001 From: taeng0204 Date: Wed, 7 Aug 2024 13:49:48 +0900 Subject: [PATCH 05/12] Merge the basic health check function with the unknown function Simplify too many health check functions --- test/integration/health_test.go | 42 +++++++++++---------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/test/integration/health_test.go b/test/integration/health_test.go index 4e7e7265f..04295c845 100644 --- a/test/integration/health_test.go +++ b/test/integration/health_test.go @@ -48,6 +48,12 @@ func TestRPCHealthCheck(t *testing.T) { resp, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{}) assert.NoError(t, err) assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) + + // use gRPC health check for unknown service + _, err = cli.Check(context.Background(), &healthpb.HealthCheckRequest{ + Service: "unknown", + }) + assert.Error(t, err) } func TestRPCHealthCheckYorkie(t *testing.T) { @@ -107,24 +113,6 @@ func TestRPCHealthCheckHealthService(t *testing.T) { assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) } -func TestRPCHealthCheckUnknownService(t *testing.T) { - // use gRPC health check for unknown service - conn, err := grpc.Dial( - defaultServer.RPCAddr(), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - assert.NoError(t, err) - defer func() { - assert.NoError(t, conn.Close()) - }() - - cli := healthpb.NewHealthClient(conn) - _, err = cli.Check(context.Background(), &healthpb.HealthCheckRequest{ - Service: "unknown", - }) - assert.Error(t, err) -} - func TestHTTPHealthCheck(t *testing.T) { // use HTTP health check resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/") @@ -138,6 +126,14 @@ func TestHTTPHealthCheck(t *testing.T) { err = json.NewDecoder(resp.Body).Decode(&healthResp) assert.NoError(t, err) assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) + + // use HTTP health check for unknown service + resp, err = http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=unknown") + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.NoError(t, err) + assert.Equal(t, resp.StatusCode, 404) } func TestHTTPHealthCheckYorkie(t *testing.T) { @@ -184,13 +180,3 @@ func TestHTTPHealthCheckHealthService(t *testing.T) { assert.NoError(t, err) assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) } - -func TestHTTPHealthCheckUnknownService(t *testing.T) { - // use HTTP health check for unknown service - resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=unknown") - defer func() { - assert.NoError(t, resp.Body.Close()) - }() - assert.NoError(t, err) - assert.Equal(t, resp.StatusCode, 404) -} From 932cf62ad8146e3b91a9a4b3bd2e97f89e5bf3b6 Mon Sep 17 00:00:00 2001 From: taeng0204 Date: Wed, 7 Aug 2024 18:49:57 +0900 Subject: [PATCH 06/12] Restructuring the health check test function Modified test functions for each service to simplify them --- test/integration/health_test.go | 197 ++++++++++++-------------------- 1 file changed, 70 insertions(+), 127 deletions(-) diff --git a/test/integration/health_test.go b/test/integration/health_test.go index 04295c845..52fd37c03 100644 --- a/test/integration/health_test.go +++ b/test/integration/health_test.go @@ -33,31 +33,13 @@ import ( healthpb "google.golang.org/grpc/health/grpc_health_v1" ) -func TestRPCHealthCheck(t *testing.T) { - // use gRPC health check - conn, err := grpc.Dial( - defaultServer.RPCAddr(), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - assert.NoError(t, err) - defer func() { - assert.NoError(t, conn.Close()) - }() - - cli := healthpb.NewHealthClient(conn) - resp, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{}) - assert.NoError(t, err) - assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) - - // use gRPC health check for unknown service - _, err = cli.Check(context.Background(), &healthpb.HealthCheckRequest{ - Service: "unknown", - }) - assert.Error(t, err) +var services = []string{ + grpchealth.HealthV1ServiceName, + v1connect.YorkieServiceName, + v1connect.AdminServiceName, } -func TestRPCHealthCheckYorkie(t *testing.T) { - // use gRPC health check for Yorkie +func TestRPCHealthCheck(t *testing.T) { conn, err := grpc.Dial( defaultServer.RPCAddr(), grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -66,117 +48,78 @@ func TestRPCHealthCheckYorkie(t *testing.T) { defer func() { assert.NoError(t, conn.Close()) }() - cli := healthpb.NewHealthClient(conn) - resp, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{ - Service: v1connect.YorkieServiceName, - }) - assert.NoError(t, err) - assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) -} -func TestRPCHealthCheckAdmin(t *testing.T) { - // use gRPC health check for Admin - conn, err := grpc.Dial( - defaultServer.RPCAddr(), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - assert.NoError(t, err) - defer func() { - assert.NoError(t, conn.Close()) - }() - - cli := healthpb.NewHealthClient(conn) - resp, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{ - Service: v1connect.AdminServiceName, + // check default service + 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.NoError(t, err) - assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) -} -func TestRPCHealthCheckHealthService(t *testing.T) { - // use gRPC health check for health service - conn, err := grpc.Dial( - defaultServer.RPCAddr(), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - assert.NoError(t, err) - defer func() { - assert.NoError(t, conn.Close()) - }() - - cli := healthpb.NewHealthClient(conn) - resp, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{ - Service: grpchealth.HealthV1ServiceName, + // check all services + for _, s := range services { + service := s + t.Run("Service: "+service, func(t *testing.T) { + resp, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{ + Service: service, + }) + assert.NoError(t, err) + assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) + }) + } + + // check unknown service + t.Run("Service: unknown", func(t *testing.T) { + _, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{ + Service: "unknown", + }) + assert.Error(t, err) }) - assert.NoError(t, err) - assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) } func TestHTTPHealthCheck(t *testing.T) { - // use HTTP health check - resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/") - defer func() { - assert.NoError(t, resp.Body.Close()) - }() - assert.NoError(t, err) - assert.Equal(t, resp.StatusCode, 200) - - var healthResp health.CheckResponse - err = json.NewDecoder(resp.Body).Decode(&healthResp) - assert.NoError(t, err) - assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) - - // use HTTP health check for unknown service - resp, err = http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=unknown") - defer func() { - assert.NoError(t, resp.Body.Close()) - }() - assert.NoError(t, err) - assert.Equal(t, resp.StatusCode, 404) -} - -func TestHTTPHealthCheckYorkie(t *testing.T) { - // use HTTP health check for Yorkie - resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=" + v1connect.YorkieServiceName) - defer func() { - assert.NoError(t, resp.Body.Close()) - }() - assert.NoError(t, err) - assert.Equal(t, resp.StatusCode, 200) - - var healthResp health.CheckResponse - err = json.NewDecoder(resp.Body).Decode(&healthResp) - assert.NoError(t, err) - assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) -} - -func TestHTTPHealthCheckAdmin(t *testing.T) { - // use HTTP health check for Admin - resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=" + v1connect.AdminServiceName) - defer func() { - assert.NoError(t, resp.Body.Close()) - }() - assert.NoError(t, err) - assert.Equal(t, resp.StatusCode, 200) - - var healthResp health.CheckResponse - err = json.NewDecoder(resp.Body).Decode(&healthResp) - assert.NoError(t, err) - assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) -} - -func TestHTTPHealthCheckHealthService(t *testing.T) { - // use HTTP health check for health service - resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=" + grpchealth.HealthV1ServiceName) - defer func() { - assert.NoError(t, resp.Body.Close()) - }() - assert.NoError(t, err) - assert.Equal(t, resp.StatusCode, 200) + // check default service + t.Run("Service: default", func(t *testing.T) { + resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/") + assert.NoError(t, err) + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.Equal(t, resp.StatusCode, http.StatusOK) + + var healthResp health.CheckResponse + err = json.NewDecoder(resp.Body).Decode(&healthResp) + assert.NoError(t, err) + assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) + }) - var healthResp health.CheckResponse - err = json.NewDecoder(resp.Body).Decode(&healthResp) - assert.NoError(t, err) - assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) + // check all services + for _, s := range services { + service := s + t.Run("Service: "+service, func(t *testing.T) { + url := "http://" + defaultServer.RPCAddr() + "/healthz/?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) + + var healthResp health.CheckResponse + err = json.NewDecoder(resp.Body).Decode(&healthResp) + assert.NoError(t, err) + assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) + }) + } + + // check unknown service + t.Run("Service: unknown", func(t *testing.T) { + resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=unknown") + assert.NoError(t, err) + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.Equal(t, resp.StatusCode, http.StatusNotFound) + }) } From 18e682c5953abdcfbcf9c52a06fbf4c6cff7746a Mon Sep 17 00:00:00 2001 From: taeng0204 Date: Wed, 7 Aug 2024 21:39:58 +0900 Subject: [PATCH 07/12] Rename health check package and function names Rename for consistency --- server/rpc/{health/health.go => httphealth/httphealth.go} | 8 ++++---- server/rpc/server.go | 4 ++-- test/integration/health_test.go | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) rename server/rpc/{health/health.go => httphealth/httphealth.go} (88%) diff --git a/server/rpc/health/health.go b/server/rpc/httphealth/httphealth.go similarity index 88% rename from server/rpc/health/health.go rename to server/rpc/httphealth/httphealth.go index 0eb54c116..0b026cfe3 100644 --- a/server/rpc/health/health.go +++ b/server/rpc/httphealth/httphealth.go @@ -14,8 +14,8 @@ * limitations under the License. */ -// Package health uses http GET to provide a health check for the server. -package health +// Package httphealth uses http GET to provide a health check for the server. +package httphealth import ( "encoding/json" @@ -29,8 +29,8 @@ type CheckResponse struct { Status string `json:"status"` } -// NewHTTPHandler creates a new HTTP handler for health checks. -func NewHTTPHandler(checker grpchealth.Checker) (string, http.Handler) { +// NewHandler creates a new HTTP handler for health checks. +func NewHandler(checker grpchealth.Checker) (string, http.Handler) { const serviceName = "/healthz/" check := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { diff --git a/server/rpc/server.go b/server/rpc/server.go index ded0cdb8c..cfd06ad88 100644 --- a/server/rpc/server.go +++ b/server/rpc/server.go @@ -36,7 +36,7 @@ import ( "github.com/yorkie-team/yorkie/server/backend" "github.com/yorkie-team/yorkie/server/logging" "github.com/yorkie-team/yorkie/server/rpc/auth" - "github.com/yorkie-team/yorkie/server/rpc/health" + "github.com/yorkie-team/yorkie/server/rpc/httphealth" "github.com/yorkie-team/yorkie/server/rpc/interceptors" ) @@ -74,7 +74,7 @@ func NewServer(conf *Config, be *backend.Backend) (*Server, error) { mux.Handle(v1connect.NewYorkieServiceHandler(newYorkieServer(yorkieServiceCtx, be), opts...)) mux.Handle(v1connect.NewAdminServiceHandler(newAdminServer(be, tokenManager), opts...)) mux.Handle(grpchealth.NewHandler(healthChecker)) - mux.Handle(health.NewHTTPHandler(healthChecker)) + mux.Handle(httphealth.NewHandler(healthChecker)) // TODO(hackerwins): We need to provide proper http server configuration. return &Server{ conf: conf, diff --git a/test/integration/health_test.go b/test/integration/health_test.go index 52fd37c03..50dd45702 100644 --- a/test/integration/health_test.go +++ b/test/integration/health_test.go @@ -27,7 +27,7 @@ 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/health" + "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" @@ -88,7 +88,7 @@ func TestHTTPHealthCheck(t *testing.T) { }() assert.Equal(t, resp.StatusCode, http.StatusOK) - var healthResp health.CheckResponse + var healthResp httphealth.CheckResponse err = json.NewDecoder(resp.Body).Decode(&healthResp) assert.NoError(t, err) assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) @@ -106,7 +106,7 @@ func TestHTTPHealthCheck(t *testing.T) { }() assert.Equal(t, resp.StatusCode, http.StatusOK) - var healthResp health.CheckResponse + var healthResp httphealth.CheckResponse err = json.NewDecoder(resp.Body).Decode(&healthResp) assert.NoError(t, err) assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) From 495a54fcc6a336886a8d6b6d186c370dc3de3fa1 Mon Sep 17 00:00:00 2001 From: Taein Lim <101996424+taeng0204@users.noreply.github.com> Date: Wed, 7 Aug 2024 22:12:35 +0900 Subject: [PATCH 08/12] Add HTTP health check handler for server health monitoring (#952) Added the handler to allow health checks to be performed with plain HTTP GET requests needed for traditional uptime checker or load balancer, along with existing gRPC health check. --- server/rpc/httphealth/httphealth.go | 62 ++++++++++++++++++++ server/rpc/server.go | 15 +++-- test/integration/health_test.go | 91 +++++++++++++++++++++++++++-- 3 files changed, 156 insertions(+), 12 deletions(-) create mode 100644 server/rpc/httphealth/httphealth.go diff --git a/server/rpc/httphealth/httphealth.go b/server/rpc/httphealth/httphealth.go new file mode 100644 index 000000000..0b026cfe3 --- /dev/null +++ b/server/rpc/httphealth/httphealth.go @@ -0,0 +1,62 @@ +/* + * Copyright 2024 The Yorkie Authors. All rights reserved. + * + * 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 httphealth uses http GET to provide a health check for the server. +package httphealth + +import ( + "encoding/json" + "net/http" + + "connectrpc.com/grpchealth" +) + +// CheckResponse represents the response structure for health checks. +type CheckResponse struct { + Status string `json:"status"` +} + +// NewHandler creates a new HTTP handler for health checks. +func NewHandler(checker grpchealth.Checker) (string, http.Handler) { + const serviceName = "/healthz/" + check := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + var checkRequest grpchealth.CheckRequest + 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) + } + }) + return serviceName, check +} diff --git a/server/rpc/server.go b/server/rpc/server.go index 85f57b521..cfd06ad88 100644 --- a/server/rpc/server.go +++ b/server/rpc/server.go @@ -36,6 +36,7 @@ import ( "github.com/yorkie-team/yorkie/server/backend" "github.com/yorkie-team/yorkie/server/logging" "github.com/yorkie-team/yorkie/server/rpc/auth" + "github.com/yorkie-team/yorkie/server/rpc/httphealth" "github.com/yorkie-team/yorkie/server/rpc/interceptors" ) @@ -62,16 +63,18 @@ func NewServer(conf *Config, be *backend.Backend) (*Server, error) { ), } - yorkieServiceCtx, yorkieServiceCancel := context.WithCancel(context.Background()) - mux := http.NewServeMux() - mux.Handle(v1connect.NewYorkieServiceHandler(newYorkieServer(yorkieServiceCtx, be), opts...)) - mux.Handle(v1connect.NewAdminServiceHandler(newAdminServer(be, tokenManager), opts...)) - mux.Handle(grpchealth.NewHandler(grpchealth.NewStaticChecker( + healthChecker := grpchealth.NewStaticChecker( grpchealth.HealthV1ServiceName, v1connect.YorkieServiceName, v1connect.AdminServiceName, - ))) + ) + yorkieServiceCtx, yorkieServiceCancel := context.WithCancel(context.Background()) + mux := http.NewServeMux() + mux.Handle(v1connect.NewYorkieServiceHandler(newYorkieServer(yorkieServiceCtx, be), opts...)) + mux.Handle(v1connect.NewAdminServiceHandler(newAdminServer(be, tokenManager), opts...)) + mux.Handle(grpchealth.NewHandler(healthChecker)) + mux.Handle(httphealth.NewHandler(healthChecker)) // TODO(hackerwins): We need to provide proper http server configuration. return &Server{ conf: conf, diff --git a/test/integration/health_test.go b/test/integration/health_test.go index 6ee176de5..50dd45702 100644 --- a/test/integration/health_test.go +++ b/test/integration/health_test.go @@ -20,16 +20,26 @@ package integration import ( "context" + "encoding/json" + "net/http" "testing" + "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" ) -func TestHealthCheck(t *testing.T) { - // use gRPC health check +var services = []string{ + grpchealth.HealthV1ServiceName, + v1connect.YorkieServiceName, + v1connect.AdminServiceName, +} + +func TestRPCHealthCheck(t *testing.T) { conn, err := grpc.Dial( defaultServer.RPCAddr(), grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -38,9 +48,78 @@ func TestHealthCheck(t *testing.T) { defer func() { assert.NoError(t, conn.Close()) }() - cli := healthpb.NewHealthClient(conn) - resp, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{}) - assert.NoError(t, err) - assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) + + // check default service + 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) + }) + + // check all services + for _, s := range services { + service := s + t.Run("Service: "+service, func(t *testing.T) { + resp, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{ + Service: service, + }) + assert.NoError(t, err) + assert.Equal(t, resp.Status, healthpb.HealthCheckResponse_SERVING) + }) + } + + // check unknown service + t.Run("Service: unknown", func(t *testing.T) { + _, err := cli.Check(context.Background(), &healthpb.HealthCheckRequest{ + Service: "unknown", + }) + assert.Error(t, err) + }) +} + +func TestHTTPHealthCheck(t *testing.T) { + // check default service + t.Run("Service: default", func(t *testing.T) { + resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/") + assert.NoError(t, err) + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.Equal(t, resp.StatusCode, http.StatusOK) + + var healthResp httphealth.CheckResponse + err = json.NewDecoder(resp.Body).Decode(&healthResp) + assert.NoError(t, err) + assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) + }) + + // check all services + for _, s := range services { + service := s + t.Run("Service: "+service, func(t *testing.T) { + url := "http://" + defaultServer.RPCAddr() + "/healthz/?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) + + var healthResp httphealth.CheckResponse + err = json.NewDecoder(resp.Body).Decode(&healthResp) + assert.NoError(t, err) + assert.Equal(t, healthResp.Status, grpchealth.StatusServing.String()) + }) + } + + // check unknown service + t.Run("Service: unknown", func(t *testing.T) { + resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=unknown") + assert.NoError(t, err) + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.Equal(t, resp.StatusCode, http.StatusNotFound) + }) } From e650f078de447ff874f20186595170f2670f9d80 Mon Sep 17 00:00:00 2001 From: taeng0204 Date: Sat, 10 Aug 2024 18:05:32 +0900 Subject: [PATCH 09/12] Modify health check endpoint and add HEAD method Need HEAD method for UptimeRobot and fix path overlap with Envoy --- server/rpc/httphealth/httphealth.go | 10 ++++--- test/integration/health_test.go | 44 ++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/server/rpc/httphealth/httphealth.go b/server/rpc/httphealth/httphealth.go index 0b026cfe3..cc185ce89 100644 --- a/server/rpc/httphealth/httphealth.go +++ b/server/rpc/httphealth/httphealth.go @@ -31,9 +31,9 @@ type CheckResponse struct { // NewHandler creates a new HTTP handler for health checks. func NewHandler(checker grpchealth.Checker) (string, http.Handler) { - const serviceName = "/healthz/" + const serviceName = "/yorkie.v1/healthz/" 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 } @@ -54,8 +54,10 @@ func NewHandler(checker grpchealth.Checker) (string, http.Handler) { } 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 diff --git a/test/integration/health_test.go b/test/integration/health_test.go index 50dd45702..3f6b20e02 100644 --- a/test/integration/health_test.go +++ b/test/integration/health_test.go @@ -78,10 +78,10 @@ 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() + "/yorkie.v1/healthz/") assert.NoError(t, err) defer func() { assert.NoError(t, resp.Body.Close()) @@ -98,7 +98,7 @@ func TestHTTPHealthCheck(t *testing.T) { 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() + "/yorkie.v1/healthz/?service=" + service resp, err := http.Get(url) assert.NoError(t, err) defer func() { @@ -115,7 +115,43 @@ func TestHTTPHealthCheck(t *testing.T) { // check unknown service t.Run("Service: unknown", func(t *testing.T) { - resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/healthz/?service=unknown") + resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/yorkie.v1/healthz/?service=unknown") + assert.NoError(t, err) + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.Equal(t, resp.StatusCode, http.StatusNotFound) + }) +} + +func TestHTTPHEADHealthCheck(t *testing.T) { + // check default service + t.Run("Service: default", func(t *testing.T) { + resp, err := http.Head("http://" + defaultServer.RPCAddr() + "/yorkie.v1/healthz/") + assert.NoError(t, err) + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.Equal(t, resp.StatusCode, http.StatusOK) + }) + + // check all services + for _, s := range services { + service := s + t.Run("Service: "+service, func(t *testing.T) { + url := "http://" + defaultServer.RPCAddr() + "/yorkie.v1/healthz/?service=" + service + resp, err := http.Head(url) + assert.NoError(t, err) + defer func() { + assert.NoError(t, resp.Body.Close()) + }() + assert.Equal(t, resp.StatusCode, http.StatusOK) + }) + } + + // check unknown service + t.Run("Service: unknown", func(t *testing.T) { + resp, err := http.Head("http://" + defaultServer.RPCAddr() + "/yorkie.v1/healthz/?service=unknown") assert.NoError(t, err) defer func() { assert.NoError(t, resp.Body.Close()) From 592302a8d78460ea4668e50a8dc6265ce6f9b655 Mon Sep 17 00:00:00 2001 From: taeng0204 Date: Sat, 10 Aug 2024 18:14:02 +0900 Subject: [PATCH 10/12] Change the order of expected and actual in a test In the existing test code, the order of expected and actual values is different, which makes it inconvenient to check the log when the test fails. --- test/integration/health_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/integration/health_test.go b/test/integration/health_test.go index 3f6b20e02..421250b4e 100644 --- a/test/integration/health_test.go +++ b/test/integration/health_test.go @@ -54,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 @@ -65,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) }) } @@ -86,12 +86,12 @@ func TestHTTPGETHealthCheck(t *testing.T) { 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 @@ -104,12 +104,12 @@ func TestHTTPGETHealthCheck(t *testing.T) { 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) }) } @@ -120,7 +120,7 @@ func TestHTTPGETHealthCheck(t *testing.T) { defer func() { assert.NoError(t, resp.Body.Close()) }() - assert.Equal(t, resp.StatusCode, http.StatusNotFound) + assert.Equal(t, http.StatusNotFound, resp.StatusCode) }) } @@ -132,7 +132,7 @@ func TestHTTPHEADHealthCheck(t *testing.T) { defer func() { assert.NoError(t, resp.Body.Close()) }() - assert.Equal(t, resp.StatusCode, http.StatusOK) + assert.Equal(t, http.StatusOK, resp.StatusCode) }) // check all services @@ -145,7 +145,7 @@ func TestHTTPHEADHealthCheck(t *testing.T) { defer func() { assert.NoError(t, resp.Body.Close()) }() - assert.Equal(t, resp.StatusCode, http.StatusOK) + assert.Equal(t, http.StatusOK, resp.StatusCode) }) } @@ -156,6 +156,6 @@ func TestHTTPHEADHealthCheck(t *testing.T) { defer func() { assert.NoError(t, resp.Body.Close()) }() - assert.Equal(t, resp.StatusCode, http.StatusNotFound) + assert.Equal(t, http.StatusNotFound, resp.StatusCode) }) } From 505a40b5e09dec17a64dd9024d21eca53a82a38f Mon Sep 17 00:00:00 2001 From: taeng0204 Date: Wed, 14 Aug 2024 00:55:02 +0900 Subject: [PATCH 11/12] Modify the health check endpoint Modified the endpoint for consistency with gRPC --- server/rpc/httphealth/httphealth.go | 5 ++++- test/integration/health_test.go | 16 ++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/server/rpc/httphealth/httphealth.go b/server/rpc/httphealth/httphealth.go index cc185ce89..aeb38aa38 100644 --- a/server/rpc/httphealth/httphealth.go +++ b/server/rpc/httphealth/httphealth.go @@ -24,6 +24,9 @@ import ( "connectrpc.com/grpchealth" ) +// 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 { Status string `json:"status"` @@ -31,7 +34,7 @@ type CheckResponse struct { // NewHandler creates a new HTTP handler for health checks. func NewHandler(checker grpchealth.Checker) (string, http.Handler) { - const serviceName = "/yorkie.v1/healthz/" + const serviceName = "/yorkie.v1.YorkieService/health" check := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet && r.Method != http.MethodHead { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) diff --git a/test/integration/health_test.go b/test/integration/health_test.go index 028c94dab..02e5fdffc 100644 --- a/test/integration/health_test.go +++ b/test/integration/health_test.go @@ -81,14 +81,14 @@ func TestRPCHealthCheck(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() + "/yorkie.v1/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, http.StatusOK, resp.StatusCode) - - var healthResp httphealth.CheckResponse + + var healthResp httphealth.CheckResponse err = json.NewDecoder(resp.Body).Decode(&healthResp) assert.NoError(t, err) assert.Equal(t, grpchealth.StatusServing.String(), healthResp.Status) @@ -98,7 +98,7 @@ func TestHTTPGETHealthCheck(t *testing.T) { for _, s := range services { service := s t.Run("Service: "+service, func(t *testing.T) { - url := "http://" + defaultServer.RPCAddr() + "/yorkie.v1/healthz/?service=" + service + url := "http://" + defaultServer.RPCAddr() + httphealth.HealthV1ServiceName + "?service=" + service resp, err := http.Get(url) assert.NoError(t, err) defer func() { @@ -115,7 +115,7 @@ func TestHTTPGETHealthCheck(t *testing.T) { // check unknown service t.Run("Service: unknown", func(t *testing.T) { - resp, err := http.Get("http://" + defaultServer.RPCAddr() + "/yorkie.v1/healthz/?service=unknown") + resp, err := http.Get("http://" + defaultServer.RPCAddr() + httphealth.HealthV1ServiceName + "?service=unknown") assert.NoError(t, err) defer func() { assert.NoError(t, resp.Body.Close()) @@ -127,7 +127,7 @@ func TestHTTPGETHealthCheck(t *testing.T) { func TestHTTPHEADHealthCheck(t *testing.T) { // check default service t.Run("Service: default", func(t *testing.T) { - resp, err := http.Head("http://" + defaultServer.RPCAddr() + "/yorkie.v1/healthz/") + resp, err := http.Head("http://" + defaultServer.RPCAddr() + httphealth.HealthV1ServiceName) assert.NoError(t, err) defer func() { assert.NoError(t, resp.Body.Close()) @@ -139,7 +139,7 @@ func TestHTTPHEADHealthCheck(t *testing.T) { for _, s := range services { service := s t.Run("Service: "+service, func(t *testing.T) { - url := "http://" + defaultServer.RPCAddr() + "/yorkie.v1/healthz/?service=" + service + url := "http://" + defaultServer.RPCAddr() + httphealth.HealthV1ServiceName + "?service=" + service resp, err := http.Head(url) assert.NoError(t, err) defer func() { @@ -151,7 +151,7 @@ func TestHTTPHEADHealthCheck(t *testing.T) { // check unknown service t.Run("Service: unknown", func(t *testing.T) { - resp, err := http.Head("http://" + defaultServer.RPCAddr() + "/yorkie.v1/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()) From 9573d8ff382d7b66ebd7bdf31b08c4b2ae5a2939 Mon Sep 17 00:00:00 2001 From: taeng0204 Date: Thu, 15 Aug 2024 16:28:25 +0900 Subject: [PATCH 12/12] Delete unnecessary service name variables We only left one duplicate const service name. --- server/rpc/httphealth/httphealth.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/rpc/httphealth/httphealth.go b/server/rpc/httphealth/httphealth.go index aeb38aa38..ab3a5a0d6 100644 --- a/server/rpc/httphealth/httphealth.go +++ b/server/rpc/httphealth/httphealth.go @@ -34,7 +34,6 @@ type CheckResponse struct { // NewHandler creates a new HTTP handler for health checks. func NewHandler(checker grpchealth.Checker) (string, http.Handler) { - const serviceName = "/yorkie.v1.YorkieService/health" check := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet && r.Method != http.MethodHead { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) @@ -63,5 +62,5 @@ func NewHandler(checker grpchealth.Checker) (string, http.Handler) { } } }) - return serviceName, check + return HealthV1ServiceName, check }