Skip to content

Commit 9d31038

Browse files
authored
feat(coderd): /debug/health: add parameter to force healthcheck (#10677)
1 parent 290180b commit 9d31038

File tree

5 files changed

+82
-11
lines changed

5 files changed

+82
-11
lines changed

coderd/apidoc/docs.go

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/debug.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,22 @@ func (api *API) debugTailnet(rw http.ResponseWriter, r *http.Request) {
4141
// @Tags Debug
4242
// @Success 200 {object} healthcheck.Report
4343
// @Router /debug/health [get]
44+
// @Param force query boolean false "Force a healthcheck to run"
4445
func (api *API) debugDeploymentHealth(rw http.ResponseWriter, r *http.Request) {
4546
apiKey := httpmw.APITokenFromRequest(r)
4647
ctx, cancel := context.WithTimeout(r.Context(), api.Options.HealthcheckTimeout)
4748
defer cancel()
4849

49-
// Get cached report if it exists.
50-
if report := api.healthCheckCache.Load(); report != nil {
51-
if time.Since(report.Time) < api.Options.HealthcheckRefresh {
52-
formatHealthcheck(ctx, rw, r, report)
53-
return
50+
// Check if the forced query parameter is set.
51+
forced := r.URL.Query().Get("force") == "true"
52+
53+
// Get cached report if it exists and the requester did not force a refresh.
54+
if !forced {
55+
if report := api.healthCheckCache.Load(); report != nil {
56+
if time.Since(report.Time) < api.Options.HealthcheckRefresh {
57+
formatHealthcheck(ctx, rw, r, report)
58+
return
59+
}
5460
}
5561
}
5662

coderd/debug_test.go

+49-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"io"
66
"net/http"
7+
"sync/atomic"
78
"testing"
89
"time"
910

@@ -22,24 +23,66 @@ func TestDebugHealth(t *testing.T) {
2223
t.Parallel()
2324

2425
var (
26+
calls = atomic.Int64{}
2527
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
2628
sessionToken string
2729
client = coderdtest.New(t, &coderdtest.Options{
2830
HealthcheckFunc: func(_ context.Context, apiKey string) *healthcheck.Report {
31+
calls.Add(1)
2932
assert.Equal(t, sessionToken, apiKey)
30-
return &healthcheck.Report{}
33+
return &healthcheck.Report{
34+
Time: time.Now(),
35+
}
3136
},
37+
HealthcheckRefresh: time.Hour, // Avoid flakes.
3238
})
3339
_ = coderdtest.CreateFirstUser(t, client)
3440
)
3541
defer cancel()
3642

3743
sessionToken = client.SessionToken()
38-
res, err := client.Request(ctx, "GET", "/api/v2/debug/health", nil)
39-
require.NoError(t, err)
40-
defer res.Body.Close()
41-
_, _ = io.ReadAll(res.Body)
42-
require.Equal(t, http.StatusOK, res.StatusCode)
44+
for i := 0; i < 10; i++ {
45+
res, err := client.Request(ctx, "GET", "/api/v2/debug/health", nil)
46+
require.NoError(t, err)
47+
_, _ = io.ReadAll(res.Body)
48+
res.Body.Close()
49+
require.Equal(t, http.StatusOK, res.StatusCode)
50+
}
51+
// The healthcheck should only have been called once.
52+
require.EqualValues(t, 1, calls.Load())
53+
})
54+
55+
t.Run("Forced", func(t *testing.T) {
56+
t.Parallel()
57+
58+
var (
59+
calls = atomic.Int64{}
60+
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
61+
sessionToken string
62+
client = coderdtest.New(t, &coderdtest.Options{
63+
HealthcheckFunc: func(_ context.Context, apiKey string) *healthcheck.Report {
64+
calls.Add(1)
65+
assert.Equal(t, sessionToken, apiKey)
66+
return &healthcheck.Report{
67+
Time: time.Now(),
68+
}
69+
},
70+
HealthcheckRefresh: time.Hour, // Avoid flakes.
71+
})
72+
_ = coderdtest.CreateFirstUser(t, client)
73+
)
74+
defer cancel()
75+
76+
sessionToken = client.SessionToken()
77+
for i := 0; i < 10; i++ {
78+
res, err := client.Request(ctx, "GET", "/api/v2/debug/health?force=true", nil)
79+
require.NoError(t, err)
80+
_, _ = io.ReadAll(res.Body)
81+
res.Body.Close()
82+
require.Equal(t, http.StatusOK, res.StatusCode)
83+
}
84+
// The healthcheck func should have been called each time.
85+
require.EqualValues(t, 10, calls.Load())
4386
})
4487

4588
t.Run("Timeout", func(t *testing.T) {

docs/api/debug.md

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)