Skip to content

Commit aa1ac57

Browse files
committed
feat/add-readyz-endpoint
1 parent 314d8e9 commit aa1ac57

File tree

3 files changed

+116
-2
lines changed

3 files changed

+116
-2
lines changed

cmd/commom/commom.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ func ServiceSetup(ctx context.Context, cancelFn context.CancelFunc, logConfig, c
6262

6363
launchTerminatingListenerGoroutine(cancelFn)
6464

65-
shutdownInternalServerFn := service.RunInternalServer(ctx, viperConfig)
65+
healthDeps := service.InitHealthDependencies(viperConfig)
66+
shutdownInternalServerFn := service.RunInternalServer(ctx, viperConfig, healthDeps)
6667

6768
return nil, viperConfig, shutdownInternalServerFn
6869
}

internal/service/health.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// MIT License
2+
//
3+
// Copyright (c) 2021 TFG Co
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
package service
24+
25+
import (
26+
"context"
27+
"time"
28+
29+
"github.com/go-pg/pg/v10"
30+
"github.com/go-redis/redis/v8"
31+
"github.com/topfreegames/maestro/internal/config"
32+
)
33+
34+
type HealthDependencies struct {
35+
PostgresDB *pg.DB
36+
RedisClient *redis.Client
37+
}
38+
39+
func InitHealthDependencies(c config.Config) *HealthDependencies {
40+
deps := &HealthDependencies{}
41+
42+
if pgDB, err := initPostgresForHealth(c); err == nil {
43+
deps.PostgresDB = pgDB
44+
}
45+
46+
if redisClient, err := initRedisForHealth(c); err == nil {
47+
deps.RedisClient = redisClient
48+
}
49+
50+
return deps
51+
}
52+
53+
func initPostgresForHealth(c config.Config) (*pg.DB, error) {
54+
opts, err := connectToPostgres(c, GetSchedulerStoragePostgresURL(c))
55+
if err != nil {
56+
return nil, err
57+
}
58+
return pg.Connect(opts), nil
59+
}
60+
61+
func initRedisForHealth(c config.Config) (*redis.Client, error) {
62+
redisURL := c.GetString(schedulerCacheRedisURLPath)
63+
if redisURL == "" {
64+
redisURL = c.GetString(roomStorageRedisURLPath)
65+
}
66+
if redisURL == "" {
67+
redisURL = c.GetString(operationStorageRedisURLPath)
68+
}
69+
70+
return createRedisClient(c, redisURL, func(opts *redis.Options) {
71+
opts.MaxRetries = 3
72+
})
73+
}
74+
75+
func checkPostgres(ctx context.Context, db *pg.DB) error {
76+
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
77+
defer cancel()
78+
_, err := db.ExecContext(ctx, "SELECT 1")
79+
return err
80+
}
81+
82+
func checkRedis(ctx context.Context, client *redis.Client) error {
83+
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
84+
defer cancel()
85+
return client.Ping(ctx).Err()
86+
}

internal/service/internal_api.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ import (
3636
// shutdown function. The internal server contains handlers for:
3737
// - health check
3838
// - metrics
39-
func RunInternalServer(ctx context.Context, configs config.Config) func() error {
39+
func RunInternalServer(ctx context.Context, configs config.Config, healthDeps *HealthDependencies) func() error {
4040
mux := http.NewServeMux()
4141
if configs.GetBool("internalApi.healthcheck.enabled") {
4242
zap.L().Info("adding healthcheck handler to internal API")
4343
mux.HandleFunc("/health", handleHealth)
4444
mux.HandleFunc("/healthz", handleHealth)
45+
mux.HandleFunc("/readyz", handleReadyz(healthDeps))
4546
}
4647
if configs.GetBool("internalApi.metrics.enabled") {
4748
zap.L().Info("adding metrics handler to internal API")
@@ -69,6 +70,32 @@ func RunInternalServer(ctx context.Context, configs config.Config) func() error
6970
}
7071
}
7172

73+
func handleReadyz(deps *HealthDependencies) http.HandlerFunc {
74+
return func(w http.ResponseWriter, r *http.Request) {
75+
healthy := true
76+
77+
if deps.PostgresDB != nil {
78+
if err := checkPostgres(r.Context(), deps.PostgresDB); err != nil {
79+
healthy = false
80+
}
81+
}
82+
83+
if deps.RedisClient != nil {
84+
if err := checkRedis(r.Context(), deps.RedisClient); err != nil {
85+
healthy = false
86+
}
87+
}
88+
89+
if !healthy {
90+
w.WriteHeader(http.StatusServiceUnavailable)
91+
}
92+
93+
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
94+
w.WriteHeader(http.StatusOK)
95+
_, _ = w.Write([]byte("OK"))
96+
}
97+
}
98+
7299
func handleHealth(w http.ResponseWriter, r *http.Request) {
73100
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
74101
w.WriteHeader(http.StatusOK)

0 commit comments

Comments
 (0)