mirror of https://github.com/coder/coder.git
feat: include health severity in reports (#10817)
This commit is contained in:
parent
e311e9ec24
commit
78df68348a
|
@ -12103,6 +12103,7 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"node": {
|
||||
|
@ -12117,6 +12118,18 @@ const docTemplate = `{
|
|||
"round_trip_ping_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"severity": {
|
||||
"enum": [
|
||||
"ok",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stun": {
|
||||
"$ref": "#/definitions/derphealth.StunReport"
|
||||
},
|
||||
|
@ -12138,6 +12151,7 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"node_reports": {
|
||||
|
@ -12149,6 +12163,18 @@ const docTemplate = `{
|
|||
"region": {
|
||||
"$ref": "#/definitions/tailcfg.DERPRegion"
|
||||
},
|
||||
"severity": {
|
||||
"enum": [
|
||||
"ok",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"warnings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
@ -12164,6 +12190,7 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"netcheck": {
|
||||
|
@ -12184,6 +12211,18 @@ const docTemplate = `{
|
|||
"$ref": "#/definitions/derphealth.RegionReport"
|
||||
}
|
||||
},
|
||||
"severity": {
|
||||
"enum": [
|
||||
"ok",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"warnings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
@ -12206,6 +12245,19 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"health.Severity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ok",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"SeverityOK",
|
||||
"SeverityWarning",
|
||||
"SeverityError"
|
||||
]
|
||||
},
|
||||
"healthcheck.AccessURLReport": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -12216,6 +12268,7 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"healthz_response": {
|
||||
|
@ -12224,6 +12277,18 @@ const docTemplate = `{
|
|||
"reachable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"severity": {
|
||||
"enum": [
|
||||
"ok",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status_code": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -12242,6 +12307,7 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"latency": {
|
||||
|
@ -12253,6 +12319,18 @@ const docTemplate = `{
|
|||
"reachable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"severity": {
|
||||
"enum": [
|
||||
"ok",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"threshold_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -12288,9 +12366,22 @@ const docTemplate = `{
|
|||
}
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is true if the report returns no errors.",
|
||||
"description": "Healthy is true if the report returns no errors.\nDeprecated: use ` + "`" + `Severity` + "`" + ` instead",
|
||||
"type": "boolean"
|
||||
},
|
||||
"severity": {
|
||||
"description": "Severity indicates the status of Coder health.",
|
||||
"enum": [
|
||||
"ok",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"description": "Time is the time the report was generated at.",
|
||||
"type": "string"
|
||||
|
@ -12313,8 +12404,21 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"severity": {
|
||||
"enum": [
|
||||
"ok",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"warnings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
|
@ -11024,6 +11024,7 @@
|
|||
"type": "string"
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"node": {
|
||||
|
@ -11038,6 +11039,14 @@
|
|||
"round_trip_ping_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"severity": {
|
||||
"enum": ["ok", "warning", "error"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stun": {
|
||||
"$ref": "#/definitions/derphealth.StunReport"
|
||||
},
|
||||
|
@ -11059,6 +11068,7 @@
|
|||
"type": "string"
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"node_reports": {
|
||||
|
@ -11070,6 +11080,14 @@
|
|||
"region": {
|
||||
"$ref": "#/definitions/tailcfg.DERPRegion"
|
||||
},
|
||||
"severity": {
|
||||
"enum": ["ok", "warning", "error"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"warnings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
@ -11085,6 +11103,7 @@
|
|||
"type": "string"
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"netcheck": {
|
||||
|
@ -11105,6 +11124,14 @@
|
|||
"$ref": "#/definitions/derphealth.RegionReport"
|
||||
}
|
||||
},
|
||||
"severity": {
|
||||
"enum": ["ok", "warning", "error"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"warnings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
@ -11127,6 +11154,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"health.Severity": {
|
||||
"type": "string",
|
||||
"enum": ["ok", "warning", "error"],
|
||||
"x-enum-varnames": ["SeverityOK", "SeverityWarning", "SeverityError"]
|
||||
},
|
||||
"healthcheck.AccessURLReport": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -11137,6 +11169,7 @@
|
|||
"type": "string"
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"healthz_response": {
|
||||
|
@ -11145,6 +11178,14 @@
|
|||
"reachable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"severity": {
|
||||
"enum": ["ok", "warning", "error"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status_code": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -11163,6 +11204,7 @@
|
|||
"type": "string"
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"latency": {
|
||||
|
@ -11174,6 +11216,14 @@
|
|||
"reachable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"severity": {
|
||||
"enum": ["ok", "warning", "error"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"threshold_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -11209,9 +11259,18 @@
|
|||
}
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is true if the report returns no errors.",
|
||||
"description": "Healthy is true if the report returns no errors.\nDeprecated: use `Severity` instead",
|
||||
"type": "boolean"
|
||||
},
|
||||
"severity": {
|
||||
"description": "Severity indicates the status of Coder health.",
|
||||
"enum": ["ok", "warning", "error"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"description": "Time is the time the report was generated at.",
|
||||
"type": "string"
|
||||
|
@ -11234,8 +11293,17 @@
|
|||
"type": "string"
|
||||
},
|
||||
"healthy": {
|
||||
"description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"severity": {
|
||||
"enum": ["ok", "warning", "error"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/health.Severity"
|
||||
}
|
||||
]
|
||||
},
|
||||
"warnings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
|
@ -9,13 +9,16 @@ import (
|
|||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||
)
|
||||
|
||||
// @typescript-generate AccessURLReport
|
||||
type AccessURLReport struct {
|
||||
Healthy bool `json:"healthy"`
|
||||
Warnings []string `json:"warnings"`
|
||||
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
|
||||
Healthy bool `json:"healthy"`
|
||||
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
AccessURL string `json:"access_url"`
|
||||
Reachable bool `json:"reachable"`
|
||||
|
@ -33,9 +36,11 @@ func (r *AccessURLReport) Run(ctx context.Context, opts *AccessURLReportOptions)
|
|||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
r.Severity = health.SeverityOK
|
||||
r.Warnings = []string{}
|
||||
if opts.AccessURL == nil {
|
||||
r.Error = ptr.Ref("access URL is nil")
|
||||
r.Severity = health.SeverityError
|
||||
return
|
||||
}
|
||||
r.AccessURL = opts.AccessURL.String()
|
||||
|
@ -47,18 +52,21 @@ func (r *AccessURLReport) Run(ctx context.Context, opts *AccessURLReportOptions)
|
|||
accessURL, err := opts.AccessURL.Parse("/healthz")
|
||||
if err != nil {
|
||||
r.Error = convertError(xerrors.Errorf("parse healthz endpoint: %w", err))
|
||||
r.Severity = health.SeverityError
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", accessURL.String(), nil)
|
||||
if err != nil {
|
||||
r.Error = convertError(xerrors.Errorf("create healthz request: %w", err))
|
||||
r.Severity = health.SeverityError
|
||||
return
|
||||
}
|
||||
|
||||
res, err := opts.Client.Do(req)
|
||||
if err != nil {
|
||||
r.Error = convertError(xerrors.Errorf("get healthz endpoint: %w", err))
|
||||
r.Severity = health.SeverityError
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
@ -66,11 +74,15 @@ func (r *AccessURLReport) Run(ctx context.Context, opts *AccessURLReportOptions)
|
|||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
r.Error = convertError(xerrors.Errorf("read healthz response: %w", err))
|
||||
r.Severity = health.SeverityError
|
||||
return
|
||||
}
|
||||
|
||||
r.Reachable = true
|
||||
r.Healthy = res.StatusCode == http.StatusOK
|
||||
r.StatusCode = res.StatusCode
|
||||
if res.StatusCode != http.StatusOK {
|
||||
r.Severity = health.SeverityWarning
|
||||
}
|
||||
r.HealthzResponse = string(body)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/healthcheck"
|
||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||
)
|
||||
|
||||
func TestAccessURL(t *testing.T) {
|
||||
|
@ -34,6 +35,7 @@ func TestAccessURL(t *testing.T) {
|
|||
|
||||
assert.True(t, report.Healthy)
|
||||
assert.True(t, report.Reachable)
|
||||
assert.Equal(t, health.SeverityOK, report.Severity)
|
||||
assert.Equal(t, http.StatusOK, report.StatusCode)
|
||||
assert.Equal(t, "OK", report.HealthzResponse)
|
||||
assert.Nil(t, report.Error)
|
||||
|
@ -64,6 +66,7 @@ func TestAccessURL(t *testing.T) {
|
|||
|
||||
assert.False(t, report.Healthy)
|
||||
assert.True(t, report.Reachable)
|
||||
assert.Equal(t, health.SeverityWarning, report.Severity)
|
||||
assert.Equal(t, http.StatusNotFound, report.StatusCode)
|
||||
assert.Equal(t, string(resp), report.HealthzResponse)
|
||||
assert.Nil(t, report.Error)
|
||||
|
@ -100,6 +103,7 @@ func TestAccessURL(t *testing.T) {
|
|||
|
||||
assert.False(t, report.Healthy)
|
||||
assert.False(t, report.Reachable)
|
||||
assert.Equal(t, health.SeverityError, report.Severity)
|
||||
assert.Equal(t, 0, report.StatusCode)
|
||||
assert.Equal(t, "", report.HealthzResponse)
|
||||
require.NotNil(t, report.Error)
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -16,8 +17,10 @@ const (
|
|||
|
||||
// @typescript-generate DatabaseReport
|
||||
type DatabaseReport struct {
|
||||
Healthy bool `json:"healthy"`
|
||||
Warnings []string `json:"warnings"`
|
||||
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
|
||||
Healthy bool `json:"healthy"`
|
||||
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
Reachable bool `json:"reachable"`
|
||||
Latency string `json:"latency"`
|
||||
|
@ -33,6 +36,7 @@ type DatabaseReportOptions struct {
|
|||
|
||||
func (r *DatabaseReport) Run(ctx context.Context, opts *DatabaseReportOptions) {
|
||||
r.Warnings = []string{}
|
||||
r.Severity = health.SeverityOK
|
||||
r.ThresholdMS = opts.Threshold.Milliseconds()
|
||||
if r.ThresholdMS == 0 {
|
||||
r.ThresholdMS = DatabaseDefaultThreshold.Milliseconds()
|
||||
|
@ -47,6 +51,7 @@ func (r *DatabaseReport) Run(ctx context.Context, opts *DatabaseReportOptions) {
|
|||
pong, err := opts.DB.Ping(ctx)
|
||||
if err != nil {
|
||||
r.Error = convertError(xerrors.Errorf("ping: %w", err))
|
||||
r.Severity = health.SeverityError
|
||||
return
|
||||
}
|
||||
pings = append(pings, pong)
|
||||
|
@ -57,8 +62,9 @@ func (r *DatabaseReport) Run(ctx context.Context, opts *DatabaseReportOptions) {
|
|||
latency := pings[pingCount/2]
|
||||
r.Latency = latency.String()
|
||||
r.LatencyMS = latency.Milliseconds()
|
||||
if r.LatencyMS < r.ThresholdMS {
|
||||
r.Healthy = true
|
||||
if r.LatencyMS >= r.ThresholdMS {
|
||||
r.Severity = health.SeverityWarning
|
||||
}
|
||||
r.Healthy = true
|
||||
r.Reachable = true
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/coder/coder/v2/coderd/database/dbmock"
|
||||
"github.com/coder/coder/v2/coderd/healthcheck"
|
||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
|
@ -35,6 +36,7 @@ func TestDatabase(t *testing.T) {
|
|||
|
||||
assert.True(t, report.Healthy)
|
||||
assert.True(t, report.Reachable)
|
||||
assert.Equal(t, health.SeverityOK, report.Severity)
|
||||
assert.Equal(t, ping.String(), report.Latency)
|
||||
assert.Equal(t, ping.Milliseconds(), report.LatencyMS)
|
||||
assert.Equal(t, healthcheck.DatabaseDefaultThreshold.Milliseconds(), report.ThresholdMS)
|
||||
|
@ -58,6 +60,7 @@ func TestDatabase(t *testing.T) {
|
|||
|
||||
assert.False(t, report.Healthy)
|
||||
assert.False(t, report.Reachable)
|
||||
assert.Equal(t, health.SeverityError, report.Severity)
|
||||
assert.Zero(t, report.Latency)
|
||||
require.NotNil(t, report.Error)
|
||||
assert.Equal(t, healthcheck.DatabaseDefaultThreshold.Milliseconds(), report.ThresholdMS)
|
||||
|
@ -84,6 +87,7 @@ func TestDatabase(t *testing.T) {
|
|||
|
||||
assert.True(t, report.Healthy)
|
||||
assert.True(t, report.Reachable)
|
||||
assert.Equal(t, health.SeverityOK, report.Severity)
|
||||
assert.Equal(t, time.Millisecond.String(), report.Latency)
|
||||
assert.EqualValues(t, 1, report.LatencyMS)
|
||||
assert.Equal(t, healthcheck.DatabaseDefaultThreshold.Milliseconds(), report.ThresholdMS)
|
||||
|
@ -108,8 +112,9 @@ func TestDatabase(t *testing.T) {
|
|||
|
||||
report.Run(ctx, &healthcheck.DatabaseReportOptions{DB: db, Threshold: time.Second})
|
||||
|
||||
assert.False(t, report.Healthy)
|
||||
assert.True(t, report.Healthy)
|
||||
assert.True(t, report.Reachable)
|
||||
assert.Equal(t, health.SeverityWarning, report.Severity)
|
||||
assert.Equal(t, time.Second.String(), report.Latency)
|
||||
assert.EqualValues(t, 1000, report.LatencyMS)
|
||||
assert.Equal(t, time.Second.Milliseconds(), report.ThresholdMS)
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"tailscale.com/types/key"
|
||||
tslogger "tailscale.com/types/logger"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||
"github.com/coder/coder/v2/coderd/util/slice"
|
||||
)
|
||||
|
@ -29,12 +30,15 @@ import (
|
|||
const (
|
||||
warningNodeUsesWebsocket = `Node uses WebSockets because the "Upgrade: DERP" header may be blocked on the load balancer.`
|
||||
oneNodeUnhealthy = "Region is operational, but performance might be degraded as one node is unhealthy."
|
||||
missingNodeReport = "Missing node health report, probably a developer error."
|
||||
)
|
||||
|
||||
// @typescript-generate Report
|
||||
type Report struct {
|
||||
Healthy bool `json:"healthy"`
|
||||
Warnings []string `json:"warnings"`
|
||||
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
|
||||
Healthy bool `json:"healthy"`
|
||||
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
Regions map[int]*RegionReport `json:"regions"`
|
||||
|
||||
|
@ -47,9 +51,12 @@ type Report struct {
|
|||
|
||||
// @typescript-generate RegionReport
|
||||
type RegionReport struct {
|
||||
mu sync.Mutex
|
||||
Healthy bool `json:"healthy"`
|
||||
Warnings []string `json:"warnings"`
|
||||
mu sync.Mutex
|
||||
|
||||
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
|
||||
Healthy bool `json:"healthy"`
|
||||
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
Region *tailcfg.DERPRegion `json:"region"`
|
||||
NodeReports []*NodeReport `json:"node_reports"`
|
||||
|
@ -61,8 +68,10 @@ type NodeReport struct {
|
|||
mu sync.Mutex
|
||||
clientCounter int
|
||||
|
||||
Healthy bool `json:"healthy"`
|
||||
Warnings []string `json:"warnings"`
|
||||
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
|
||||
Healthy bool `json:"healthy"`
|
||||
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
Node *tailcfg.DERPNode `json:"node"`
|
||||
|
||||
|
@ -91,6 +100,8 @@ type ReportOptions struct {
|
|||
|
||||
func (r *Report) Run(ctx context.Context, opts *ReportOptions) {
|
||||
r.Healthy = true
|
||||
r.Severity = health.SeverityOK
|
||||
|
||||
r.Regions = map[int]*RegionReport{}
|
||||
r.Warnings = []string{}
|
||||
|
||||
|
@ -142,14 +153,22 @@ func (r *Report) Run(ctx context.Context, opts *ReportOptions) {
|
|||
r.NetcheckErr = convertError(netcheckErr)
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Review region reports and select the highest severity.
|
||||
for _, regionReport := range r.Regions {
|
||||
if regionReport.Severity.Value() > r.Severity.Value() {
|
||||
r.Severity = regionReport.Severity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RegionReport) Run(ctx context.Context) {
|
||||
r.Healthy = true
|
||||
r.Severity = health.SeverityOK
|
||||
r.NodeReports = []*NodeReport{}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
var healthyNodes int // atomic.Int64 is not mandatory as we depend on RegionReport mutex.
|
||||
var unhealthyNodes int // atomic.Int64 is not mandatory as we depend on RegionReport mutex.
|
||||
|
||||
wg.Add(len(r.Region.Nodes))
|
||||
for _, node := range r.Region.Nodes {
|
||||
|
@ -166,6 +185,7 @@ func (r *RegionReport) Run(ctx context.Context) {
|
|||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
nodeReport.Error = ptr.Ref(fmt.Sprint(err))
|
||||
nodeReport.Severity = health.SeverityError
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -173,8 +193,8 @@ func (r *RegionReport) Run(ctx context.Context) {
|
|||
|
||||
r.mu.Lock()
|
||||
r.NodeReports = append(r.NodeReports, &nodeReport)
|
||||
if nodeReport.Healthy {
|
||||
healthyNodes++
|
||||
if nodeReport.Severity != health.SeverityOK {
|
||||
unhealthyNodes++
|
||||
}
|
||||
|
||||
for _, w := range nodeReport.Warnings {
|
||||
|
@ -190,11 +210,29 @@ func (r *RegionReport) Run(ctx context.Context) {
|
|||
|
||||
sortNodeReports(r.NodeReports)
|
||||
|
||||
// Coder allows for 1 unhealthy node in the region, unless there is only 1 node.
|
||||
if len(r.Region.Nodes) != len(r.NodeReports) {
|
||||
r.Healthy = false
|
||||
r.Severity = health.SeverityError
|
||||
r.Error = ptr.Ref(missingNodeReport)
|
||||
return
|
||||
}
|
||||
|
||||
if len(r.Region.Nodes) == 1 {
|
||||
r.Healthy = healthyNodes == len(r.Region.Nodes)
|
||||
} else if healthyNodes < len(r.Region.Nodes) {
|
||||
r.Healthy = r.NodeReports[0].Severity != health.SeverityError
|
||||
r.Severity = r.NodeReports[0].Severity
|
||||
} else if unhealthyNodes == 1 {
|
||||
// r.Healthy = true (by default)
|
||||
r.Severity = health.SeverityWarning
|
||||
r.Warnings = append(r.Warnings, oneNodeUnhealthy)
|
||||
} else if unhealthyNodes > 1 {
|
||||
r.Healthy = false
|
||||
|
||||
// Review node reports and select the highest severity.
|
||||
for _, nodeReport := range r.NodeReports {
|
||||
if nodeReport.Severity.Value() > r.Severity.Value() {
|
||||
r.Severity = nodeReport.Severity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -221,6 +259,7 @@ func (r *NodeReport) Run(ctx context.Context) {
|
|||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
r.Severity = health.SeverityOK
|
||||
r.ClientLogs = [][]string{}
|
||||
r.ClientErrs = [][]string{}
|
||||
|
||||
|
@ -243,10 +282,12 @@ func (r *NodeReport) Run(ctx context.Context) {
|
|||
// The node was marked as STUN compatible but the STUN test failed.
|
||||
r.STUN.Error != nil {
|
||||
r.Healthy = false
|
||||
r.Severity = health.SeverityError
|
||||
}
|
||||
|
||||
if r.UsesWebsocket {
|
||||
r.Warnings = append(r.Warnings, warningNodeUsesWebsocket)
|
||||
r.Severity = health.SeverityWarning
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"tailscale.com/types/key"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/healthcheck/derphealth"
|
||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||
"github.com/coder/coder/v2/tailnet"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
@ -123,10 +124,13 @@ func TestDERP(t *testing.T) {
|
|||
report.Run(ctx, opts)
|
||||
|
||||
assert.True(t, report.Healthy)
|
||||
assert.Equal(t, health.SeverityWarning, report.Severity)
|
||||
for _, region := range report.Regions {
|
||||
assert.True(t, region.Healthy)
|
||||
assert.True(t, region.NodeReports[0].Healthy)
|
||||
assert.Equal(t, health.SeverityOK, region.NodeReports[0].Severity)
|
||||
assert.False(t, region.NodeReports[1].Healthy)
|
||||
assert.Equal(t, health.SeverityError, region.NodeReports[1].Severity)
|
||||
assert.Len(t, region.Warnings, 1)
|
||||
}
|
||||
})
|
||||
|
@ -221,12 +225,15 @@ func TestDERP(t *testing.T) {
|
|||
report.Run(ctx, opts)
|
||||
|
||||
assert.True(t, report.Healthy)
|
||||
assert.Equal(t, health.SeverityWarning, report.Severity)
|
||||
assert.NotEmpty(t, report.Warnings)
|
||||
for _, region := range report.Regions {
|
||||
assert.True(t, region.Healthy)
|
||||
assert.Equal(t, health.SeverityWarning, region.Severity)
|
||||
assert.NotEmpty(t, region.Warnings)
|
||||
for _, node := range region.NodeReports {
|
||||
assert.True(t, node.Healthy)
|
||||
assert.Equal(t, health.SeverityWarning, node.Severity)
|
||||
assert.NotEmpty(t, node.Warnings)
|
||||
assert.True(t, node.CanExchangeMessages)
|
||||
assert.NotEmpty(t, node.RoundTripPing)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package derphealth
|
||||
|
||||
// DERP healthcheck is kept in a separate package as it is used by `cli/netcheck.go`,
|
||||
// which is part of the slim binary. Slim binary can't have dependency on `database`,
|
||||
// which is used by the database healthcheck.
|
|
@ -0,0 +1,20 @@
|
|||
package health
|
||||
|
||||
const (
|
||||
SeverityOK Severity = "ok"
|
||||
SeverityWarning Severity = "warning"
|
||||
SeverityError Severity = "error"
|
||||
)
|
||||
|
||||
// @typescript-generate Severity
|
||||
type Severity string
|
||||
|
||||
var severityRank = map[Severity]int{
|
||||
SeverityOK: 0,
|
||||
SeverityWarning: 1,
|
||||
SeverityError: 2,
|
||||
}
|
||||
|
||||
func (s Severity) Value() int {
|
||||
return severityRank[s]
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/coder/coder/v2/buildinfo"
|
||||
"github.com/coder/coder/v2/coderd/healthcheck/derphealth"
|
||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||
)
|
||||
|
||||
|
@ -30,7 +31,10 @@ type Report struct {
|
|||
// Time is the time the report was generated at.
|
||||
Time time.Time `json:"time"`
|
||||
// Healthy is true if the report returns no errors.
|
||||
// Deprecated: use `Severity` instead
|
||||
Healthy bool `json:"healthy"`
|
||||
// Severity indicates the status of Coder health.
|
||||
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
|
||||
// FailingSections is a list of sections that have failed their healthcheck.
|
||||
FailingSections []string `json:"failing_sections"`
|
||||
|
||||
|
@ -151,6 +155,22 @@ func Run(ctx context.Context, opts *ReportOptions) *Report {
|
|||
}
|
||||
|
||||
report.Healthy = len(report.FailingSections) == 0
|
||||
|
||||
// Review healthcheck sub-reports.
|
||||
report.Severity = health.SeverityOK
|
||||
|
||||
if report.DERP.Severity.Value() > report.Severity.Value() {
|
||||
report.Severity = report.DERP.Severity
|
||||
}
|
||||
if report.AccessURL.Severity.Value() > report.Severity.Value() {
|
||||
report.Severity = report.AccessURL.Severity
|
||||
}
|
||||
if report.Websocket.Severity.Value() > report.Severity.Value() {
|
||||
report.Severity = report.Websocket.Severity
|
||||
}
|
||||
if report.Database.Severity.Value() > report.Severity.Value() {
|
||||
report.Severity = report.Database.Severity
|
||||
}
|
||||
return &report
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ import (
|
|||
|
||||
"golang.org/x/xerrors"
|
||||
"nhooyr.io/websocket"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||
)
|
||||
|
||||
type WebsocketReportOptions struct {
|
||||
|
@ -21,8 +23,10 @@ type WebsocketReportOptions struct {
|
|||
|
||||
// @typescript-generate WebsocketReport
|
||||
type WebsocketReport struct {
|
||||
Healthy bool `json:"healthy"`
|
||||
Warnings []string `json:"warnings"`
|
||||
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
|
||||
Healthy bool `json:"healthy"`
|
||||
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
Body string `json:"body"`
|
||||
Code int `json:"code"`
|
||||
|
@ -33,10 +37,12 @@ func (r *WebsocketReport) Run(ctx context.Context, opts *WebsocketReportOptions)
|
|||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
r.Severity = health.SeverityOK
|
||||
r.Warnings = []string{}
|
||||
u, err := opts.AccessURL.Parse("/api/v2/debug/ws")
|
||||
if err != nil {
|
||||
r.Error = convertError(xerrors.Errorf("parse access url: %w", err))
|
||||
r.Severity = health.SeverityError
|
||||
return
|
||||
}
|
||||
if u.Scheme == "https" {
|
||||
|
@ -64,6 +70,7 @@ func (r *WebsocketReport) Run(ctx context.Context, opts *WebsocketReportOptions)
|
|||
}
|
||||
if err != nil {
|
||||
r.Error = convertError(xerrors.Errorf("websocket dial: %w", err))
|
||||
r.Severity = health.SeverityError
|
||||
return
|
||||
}
|
||||
defer c.Close(websocket.StatusGoingAway, "goodbye")
|
||||
|
@ -73,22 +80,26 @@ func (r *WebsocketReport) Run(ctx context.Context, opts *WebsocketReportOptions)
|
|||
err := c.Write(ctx, websocket.MessageText, []byte(msg))
|
||||
if err != nil {
|
||||
r.Error = convertError(xerrors.Errorf("write message: %w", err))
|
||||
r.Severity = health.SeverityError
|
||||
return
|
||||
}
|
||||
|
||||
ty, got, err := c.Read(ctx)
|
||||
if err != nil {
|
||||
r.Error = convertError(xerrors.Errorf("read message: %w", err))
|
||||
r.Severity = health.SeverityError
|
||||
return
|
||||
}
|
||||
|
||||
if ty != websocket.MessageText {
|
||||
r.Error = convertError(xerrors.Errorf("received incorrect message type: %v", ty))
|
||||
r.Severity = health.SeverityError
|
||||
return
|
||||
}
|
||||
|
||||
if string(got) != msg {
|
||||
r.Error = convertError(xerrors.Errorf("received incorrect message: wanted %q, got %q", msg, string(got)))
|
||||
r.Severity = health.SeverityError
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/healthcheck"
|
||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
|
@ -63,6 +64,7 @@ func TestWebsocket(t *testing.T) {
|
|||
})
|
||||
|
||||
require.NotNil(t, wsReport.Error)
|
||||
require.Equal(t, health.SeverityError, wsReport.Severity)
|
||||
assert.Equal(t, wsReport.Body, "test error")
|
||||
assert.Equal(t, wsReport.Code, http.StatusBadRequest)
|
||||
})
|
||||
|
|
|
@ -51,6 +51,7 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
|
|||
"healthy": true,
|
||||
"healthz_response": "string",
|
||||
"reachable": true,
|
||||
"severity": "ok",
|
||||
"status_code": 0,
|
||||
"warnings": ["string"]
|
||||
},
|
||||
|
@ -61,6 +62,7 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
|
|||
"latency": "string",
|
||||
"latency_ms": 0,
|
||||
"reachable": true,
|
||||
"severity": "ok",
|
||||
"threshold_ms": 0,
|
||||
"warnings": ["string"]
|
||||
},
|
||||
|
@ -131,6 +133,7 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
|
|||
},
|
||||
"round_trip_ping": "string",
|
||||
"round_trip_ping_ms": 0,
|
||||
"severity": "ok",
|
||||
"stun": {
|
||||
"canSTUN": true,
|
||||
"enabled": true,
|
||||
|
@ -164,6 +167,7 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
|
|||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
},
|
||||
"property2": {
|
||||
|
@ -197,6 +201,7 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
|
|||
},
|
||||
"round_trip_ping": "string",
|
||||
"round_trip_ping_ms": 0,
|
||||
"severity": "ok",
|
||||
"stun": {
|
||||
"canSTUN": true,
|
||||
"enabled": true,
|
||||
|
@ -230,19 +235,23 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
|
|||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
}
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
},
|
||||
"failing_sections": ["string"],
|
||||
"healthy": true,
|
||||
"severity": "ok",
|
||||
"time": "string",
|
||||
"websocket": {
|
||||
"body": "string",
|
||||
"code": 0,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7139,6 +7139,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
},
|
||||
"round_trip_ping": "string",
|
||||
"round_trip_ping_ms": 0,
|
||||
"severity": "ok",
|
||||
"stun": {
|
||||
"canSTUN": true,
|
||||
"enabled": true,
|
||||
|
@ -7151,20 +7152,29 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ----------------------- | ------------------------------------------------ | -------- | ------------ | ----------- |
|
||||
| `can_exchange_messages` | boolean | false | | |
|
||||
| `client_errs` | array of array | false | | |
|
||||
| `client_logs` | array of array | false | | |
|
||||
| `error` | string | false | | |
|
||||
| `healthy` | boolean | false | | |
|
||||
| `node` | [tailcfg.DERPNode](#tailcfgderpnode) | false | | |
|
||||
| `node_info` | [derp.ServerInfoMessage](#derpserverinfomessage) | false | | |
|
||||
| `round_trip_ping` | string | false | | |
|
||||
| `round_trip_ping_ms` | integer | false | | |
|
||||
| `stun` | [derphealth.StunReport](#derphealthstunreport) | false | | |
|
||||
| `uses_websocket` | boolean | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ----------------------- | ------------------------------------------------ | -------- | ------------ | ------------------------------------------------------------------------------------------- |
|
||||
| `can_exchange_messages` | boolean | false | | |
|
||||
| `client_errs` | array of array | false | | |
|
||||
| `client_logs` | array of array | false | | |
|
||||
| `error` | string | false | | |
|
||||
| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. |
|
||||
| `node` | [tailcfg.DERPNode](#tailcfgderpnode) | false | | |
|
||||
| `node_info` | [derp.ServerInfoMessage](#derpserverinfomessage) | false | | |
|
||||
| `round_trip_ping` | string | false | | |
|
||||
| `round_trip_ping_ms` | integer | false | | |
|
||||
| `severity` | [health.Severity](#healthseverity) | false | | |
|
||||
| `stun` | [derphealth.StunReport](#derphealthstunreport) | false | | |
|
||||
| `uses_websocket` | boolean | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
| ---------- | --------- |
|
||||
| `severity` | `ok` |
|
||||
| `severity` | `warning` |
|
||||
| `severity` | `error` |
|
||||
|
||||
## derphealth.RegionReport
|
||||
|
||||
|
@ -7200,6 +7210,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
},
|
||||
"round_trip_ping": "string",
|
||||
"round_trip_ping_ms": 0,
|
||||
"severity": "ok",
|
||||
"stun": {
|
||||
"canSTUN": true,
|
||||
"enabled": true,
|
||||
|
@ -7233,19 +7244,29 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| -------------- | ------------------------------------------------------- | -------- | ------------ | ----------- |
|
||||
| `error` | string | false | | |
|
||||
| `healthy` | boolean | false | | |
|
||||
| `node_reports` | array of [derphealth.NodeReport](#derphealthnodereport) | false | | |
|
||||
| `region` | [tailcfg.DERPRegion](#tailcfgderpregion) | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| -------------- | ------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- |
|
||||
| `error` | string | false | | |
|
||||
| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. |
|
||||
| `node_reports` | array of [derphealth.NodeReport](#derphealthnodereport) | false | | |
|
||||
| `region` | [tailcfg.DERPRegion](#tailcfgderpregion) | false | | |
|
||||
| `severity` | [health.Severity](#healthseverity) | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
| ---------- | --------- |
|
||||
| `severity` | `ok` |
|
||||
| `severity` | `warning` |
|
||||
| `severity` | `error` |
|
||||
|
||||
## derphealth.Report
|
||||
|
||||
|
@ -7317,6 +7338,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
},
|
||||
"round_trip_ping": "string",
|
||||
"round_trip_ping_ms": 0,
|
||||
"severity": "ok",
|
||||
"stun": {
|
||||
"canSTUN": true,
|
||||
"enabled": true,
|
||||
|
@ -7350,6 +7372,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
},
|
||||
"property2": {
|
||||
|
@ -7383,6 +7406,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
},
|
||||
"round_trip_ping": "string",
|
||||
"round_trip_ping_ms": 0,
|
||||
"severity": "ok",
|
||||
"stun": {
|
||||
"canSTUN": true,
|
||||
"enabled": true,
|
||||
|
@ -7416,25 +7440,36 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
}
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------ | -------------------------------------------------- | -------- | ------------ | ----------- |
|
||||
| `error` | string | false | | |
|
||||
| `healthy` | boolean | false | | |
|
||||
| `netcheck` | [netcheck.Report](#netcheckreport) | false | | |
|
||||
| `netcheck_err` | string | false | | |
|
||||
| `netcheck_logs` | array of string | false | | |
|
||||
| `regions` | object | false | | |
|
||||
| » `[any property]` | [derphealth.RegionReport](#derphealthregionreport) | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------ | -------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- |
|
||||
| `error` | string | false | | |
|
||||
| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. |
|
||||
| `netcheck` | [netcheck.Report](#netcheckreport) | false | | |
|
||||
| `netcheck_err` | string | false | | |
|
||||
| `netcheck_logs` | array of string | false | | |
|
||||
| `regions` | object | false | | |
|
||||
| » `[any property]` | [derphealth.RegionReport](#derphealthregionreport) | false | | |
|
||||
| `severity` | [health.Severity](#healthseverity) | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
| ---------- | --------- |
|
||||
| `severity` | `ok` |
|
||||
| `severity` | `warning` |
|
||||
| `severity` | `error` |
|
||||
|
||||
## derphealth.StunReport
|
||||
|
||||
|
@ -7454,6 +7489,22 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
| `enabled` | boolean | false | | |
|
||||
| `error` | string | false | | |
|
||||
|
||||
## health.Severity
|
||||
|
||||
```json
|
||||
"ok"
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value |
|
||||
| --------- |
|
||||
| `ok` |
|
||||
| `warning` |
|
||||
| `error` |
|
||||
|
||||
## healthcheck.AccessURLReport
|
||||
|
||||
```json
|
||||
|
@ -7463,6 +7514,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
"healthy": true,
|
||||
"healthz_response": "string",
|
||||
"reachable": true,
|
||||
"severity": "ok",
|
||||
"status_code": 0,
|
||||
"warnings": ["string"]
|
||||
}
|
||||
|
@ -7470,15 +7522,24 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------ | --------------- | -------- | ------------ | ----------- |
|
||||
| `access_url` | string | false | | |
|
||||
| `error` | string | false | | |
|
||||
| `healthy` | boolean | false | | |
|
||||
| `healthz_response` | string | false | | |
|
||||
| `reachable` | boolean | false | | |
|
||||
| `status_code` | integer | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------ | ---------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- |
|
||||
| `access_url` | string | false | | |
|
||||
| `error` | string | false | | |
|
||||
| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. |
|
||||
| `healthz_response` | string | false | | |
|
||||
| `reachable` | boolean | false | | |
|
||||
| `severity` | [health.Severity](#healthseverity) | false | | |
|
||||
| `status_code` | integer | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
| ---------- | --------- |
|
||||
| `severity` | `ok` |
|
||||
| `severity` | `warning` |
|
||||
| `severity` | `error` |
|
||||
|
||||
## healthcheck.DatabaseReport
|
||||
|
||||
|
@ -7489,6 +7550,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
"latency": "string",
|
||||
"latency_ms": 0,
|
||||
"reachable": true,
|
||||
"severity": "ok",
|
||||
"threshold_ms": 0,
|
||||
"warnings": ["string"]
|
||||
}
|
||||
|
@ -7496,15 +7558,24 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| -------------- | --------------- | -------- | ------------ | ----------- |
|
||||
| `error` | string | false | | |
|
||||
| `healthy` | boolean | false | | |
|
||||
| `latency` | string | false | | |
|
||||
| `latency_ms` | integer | false | | |
|
||||
| `reachable` | boolean | false | | |
|
||||
| `threshold_ms` | integer | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| -------------- | ---------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- |
|
||||
| `error` | string | false | | |
|
||||
| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. |
|
||||
| `latency` | string | false | | |
|
||||
| `latency_ms` | integer | false | | |
|
||||
| `reachable` | boolean | false | | |
|
||||
| `severity` | [health.Severity](#healthseverity) | false | | |
|
||||
| `threshold_ms` | integer | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
| ---------- | --------- |
|
||||
| `severity` | `ok` |
|
||||
| `severity` | `warning` |
|
||||
| `severity` | `error` |
|
||||
|
||||
## healthcheck.Report
|
||||
|
||||
|
@ -7516,6 +7587,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
"healthy": true,
|
||||
"healthz_response": "string",
|
||||
"reachable": true,
|
||||
"severity": "ok",
|
||||
"status_code": 0,
|
||||
"warnings": ["string"]
|
||||
},
|
||||
|
@ -7526,6 +7598,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
"latency": "string",
|
||||
"latency_ms": 0,
|
||||
"reachable": true,
|
||||
"severity": "ok",
|
||||
"threshold_ms": 0,
|
||||
"warnings": ["string"]
|
||||
},
|
||||
|
@ -7596,6 +7669,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
},
|
||||
"round_trip_ping": "string",
|
||||
"round_trip_ping_ms": 0,
|
||||
"severity": "ok",
|
||||
"stun": {
|
||||
"canSTUN": true,
|
||||
"enabled": true,
|
||||
|
@ -7629,6 +7703,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
},
|
||||
"property2": {
|
||||
|
@ -7662,6 +7737,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
},
|
||||
"round_trip_ping": "string",
|
||||
"round_trip_ping_ms": 0,
|
||||
"severity": "ok",
|
||||
"stun": {
|
||||
"canSTUN": true,
|
||||
"enabled": true,
|
||||
|
@ -7695,19 +7771,23 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
}
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
},
|
||||
"failing_sections": ["string"],
|
||||
"healthy": true,
|
||||
"severity": "ok",
|
||||
"time": "string",
|
||||
"websocket": {
|
||||
"body": "string",
|
||||
"code": 0,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
}
|
||||
}
|
||||
|
@ -7715,16 +7795,25 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------ | ---------------------------------------------------------- | -------- | ------------ | -------------------------------------------------------------------------- |
|
||||
| `access_url` | [healthcheck.AccessURLReport](#healthcheckaccessurlreport) | false | | |
|
||||
| `coder_version` | string | false | | The Coder version of the server that the report was generated on. |
|
||||
| `database` | [healthcheck.DatabaseReport](#healthcheckdatabasereport) | false | | |
|
||||
| `derp` | [derphealth.Report](#derphealthreport) | false | | |
|
||||
| `failing_sections` | array of string | false | | Failing sections is a list of sections that have failed their healthcheck. |
|
||||
| `healthy` | boolean | false | | Healthy is true if the report returns no errors. |
|
||||
| `time` | string | false | | Time is the time the report was generated at. |
|
||||
| `websocket` | [healthcheck.WebsocketReport](#healthcheckwebsocketreport) | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------ | ---------------------------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------- |
|
||||
| `access_url` | [healthcheck.AccessURLReport](#healthcheckaccessurlreport) | false | | |
|
||||
| `coder_version` | string | false | | The Coder version of the server that the report was generated on. |
|
||||
| `database` | [healthcheck.DatabaseReport](#healthcheckdatabasereport) | false | | |
|
||||
| `derp` | [derphealth.Report](#derphealthreport) | false | | |
|
||||
| `failing_sections` | array of string | false | | Failing sections is a list of sections that have failed their healthcheck. |
|
||||
| `healthy` | boolean | false | | Healthy is true if the report returns no errors. Deprecated: use `Severity` instead |
|
||||
| `severity` | [health.Severity](#healthseverity) | false | | Severity indicates the status of Coder health. |
|
||||
| `time` | string | false | | Time is the time the report was generated at. |
|
||||
| `websocket` | [healthcheck.WebsocketReport](#healthcheckwebsocketreport) | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
| ---------- | --------- |
|
||||
| `severity` | `ok` |
|
||||
| `severity` | `warning` |
|
||||
| `severity` | `error` |
|
||||
|
||||
## healthcheck.WebsocketReport
|
||||
|
||||
|
@ -7734,19 +7823,29 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
"code": 0,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"severity": "ok",
|
||||
"warnings": ["string"]
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ---------- | --------------- | -------- | ------------ | ----------- |
|
||||
| `body` | string | false | | |
|
||||
| `code` | integer | false | | |
|
||||
| `error` | string | false | | |
|
||||
| `healthy` | boolean | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ---------- | ---------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- |
|
||||
| `body` | string | false | | |
|
||||
| `code` | integer | false | | |
|
||||
| `error` | string | false | | |
|
||||
| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. |
|
||||
| `severity` | [health.Severity](#healthseverity) | false | | |
|
||||
| `warnings` | array of string | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
| ---------- | --------- |
|
||||
| `severity` | `ok` |
|
||||
| `severity` | `warning` |
|
||||
| `severity` | `error` |
|
||||
|
||||
## netcheck.Report
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ var (
|
|||
// Do not include things like "Database", as that would break the idea
|
||||
// of splitting db and api types.
|
||||
// Only include dirs that are client facing packages.
|
||||
externalTypeDirs = [...]string{"./cli/clibase", "./coderd/healthcheck/derphealth"}
|
||||
externalTypeDirs = [...]string{"./cli/clibase", "./coderd/healthcheck/health", "./coderd/healthcheck/derphealth"}
|
||||
indent = " "
|
||||
)
|
||||
|
||||
|
|
|
@ -2084,6 +2084,7 @@ export type RegionTypes = Region | WorkspaceProxy;
|
|||
// From healthcheck/accessurl.go
|
||||
export interface HealthcheckAccessURLReport {
|
||||
readonly healthy: boolean;
|
||||
readonly severity: HealthSeverity;
|
||||
readonly warnings: string[];
|
||||
readonly access_url: string;
|
||||
readonly reachable: boolean;
|
||||
|
@ -2095,6 +2096,7 @@ export interface HealthcheckAccessURLReport {
|
|||
// From healthcheck/database.go
|
||||
export interface HealthcheckDatabaseReport {
|
||||
readonly healthy: boolean;
|
||||
readonly severity: HealthSeverity;
|
||||
readonly warnings: string[];
|
||||
readonly reachable: boolean;
|
||||
readonly latency: string;
|
||||
|
@ -2107,6 +2109,7 @@ export interface HealthcheckDatabaseReport {
|
|||
export interface HealthcheckReport {
|
||||
readonly time: string;
|
||||
readonly healthy: boolean;
|
||||
readonly severity: HealthSeverity;
|
||||
readonly failing_sections: string[];
|
||||
readonly derp: DerphealthReport;
|
||||
readonly access_url: HealthcheckAccessURLReport;
|
||||
|
@ -2118,6 +2121,7 @@ export interface HealthcheckReport {
|
|||
// From healthcheck/websocket.go
|
||||
export interface HealthcheckWebsocketReport {
|
||||
readonly healthy: boolean;
|
||||
readonly severity: HealthSeverity;
|
||||
readonly warnings: string[];
|
||||
readonly body: string;
|
||||
readonly code: number;
|
||||
|
@ -2169,11 +2173,19 @@ export const ClibaseValueSources: ClibaseValueSource[] = [
|
|||
"yaml",
|
||||
];
|
||||
|
||||
// The code below is generated from coderd/healthcheck/health.
|
||||
|
||||
// From health/model.go
|
||||
export type HealthSeverity = "error" | "ok" | "warning";
|
||||
export const HealthSeveritys: HealthSeverity[] = ["error", "ok", "warning"];
|
||||
|
||||
// The code below is generated from coderd/healthcheck/derphealth.
|
||||
|
||||
// From derphealth/derp.go
|
||||
export interface DerphealthNodeReport {
|
||||
readonly healthy: boolean;
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/v2/coderd/healthcheck/health.Severity")
|
||||
readonly severity: string;
|
||||
readonly warnings: string[];
|
||||
// Named type "tailscale.com/tailcfg.DERPNode" unknown, using "any"
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
|
||||
|
@ -2194,6 +2206,8 @@ export interface DerphealthNodeReport {
|
|||
// From derphealth/derp.go
|
||||
export interface DerphealthRegionReport {
|
||||
readonly healthy: boolean;
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/v2/coderd/healthcheck/health.Severity")
|
||||
readonly severity: string;
|
||||
readonly warnings: string[];
|
||||
// Named type "tailscale.com/tailcfg.DERPRegion" unknown, using "any"
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
|
||||
|
@ -2205,6 +2219,8 @@ export interface DerphealthRegionReport {
|
|||
// From derphealth/derp.go
|
||||
export interface DerphealthReport {
|
||||
readonly healthy: boolean;
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/v2/coderd/healthcheck/health.Severity")
|
||||
readonly severity: string;
|
||||
readonly warnings: string[];
|
||||
readonly regions: Record<number, DerphealthRegionReport>;
|
||||
// Named type "tailscale.com/net/netcheck.Report" unknown, using "any"
|
||||
|
|
|
@ -2398,13 +2398,16 @@ export const MockLicenseResponse: GetLicensesResponse[] = [
|
|||
export const MockHealth: TypesGen.HealthcheckReport = {
|
||||
time: "2023-08-01T16:51:03.29792825Z",
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
failing_sections: [],
|
||||
derp: {
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
regions: {
|
||||
"999": {
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
region: {
|
||||
EmbeddedRelay: true,
|
||||
|
@ -2431,6 +2434,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
node_reports: [
|
||||
{
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
node: {
|
||||
Name: "999stun0",
|
||||
|
@ -2456,6 +2460,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
},
|
||||
{
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
node: {
|
||||
Name: "999b",
|
||||
|
@ -2490,6 +2495,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
},
|
||||
"10007": {
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
region: {
|
||||
EmbeddedRelay: false,
|
||||
|
@ -2516,6 +2522,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
node_reports: [
|
||||
{
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
node: {
|
||||
Name: "10007stun0",
|
||||
|
@ -2541,6 +2548,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
},
|
||||
{
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
node: {
|
||||
Name: "10007a",
|
||||
|
@ -2575,6 +2583,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
},
|
||||
"10008": {
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
region: {
|
||||
EmbeddedRelay: false,
|
||||
|
@ -2601,6 +2610,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
node_reports: [
|
||||
{
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
node: {
|
||||
Name: "10008stun0",
|
||||
|
@ -2626,6 +2636,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
},
|
||||
{
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
node: {
|
||||
Name: "10008a",
|
||||
|
@ -2660,6 +2671,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
},
|
||||
"10009": {
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
region: {
|
||||
EmbeddedRelay: false,
|
||||
|
@ -2686,6 +2698,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
node_reports: [
|
||||
{
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
node: {
|
||||
Name: "10009stun0",
|
||||
|
@ -2711,6 +2724,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
},
|
||||
{
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
node: {
|
||||
Name: "10009a",
|
||||
|
@ -2790,6 +2804,7 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
},
|
||||
access_url: {
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
access_url: "https://dev.coder.com",
|
||||
reachable: true,
|
||||
|
@ -2798,12 +2813,14 @@ export const MockHealth: TypesGen.HealthcheckReport = {
|
|||
},
|
||||
websocket: {
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
body: "",
|
||||
code: 101,
|
||||
},
|
||||
database: {
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
reachable: true,
|
||||
latency: "92570",
|
||||
|
@ -2824,11 +2841,13 @@ export const MockListeningPortsResponse: TypesGen.WorkspaceAgentListeningPortsRe
|
|||
|
||||
export const DeploymentHealthUnhealthy: TypesGen.HealthcheckReport = {
|
||||
healthy: false,
|
||||
severity: "ok",
|
||||
failing_sections: [], // apparently this property is not used at all?
|
||||
time: "2023-10-12T23:15:00.000000000Z",
|
||||
coder_version: "v2.3.0-devel+8cca4915a",
|
||||
access_url: {
|
||||
healthy: true,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
access_url: "",
|
||||
healthz_response: "",
|
||||
|
@ -2837,6 +2856,7 @@ export const DeploymentHealthUnhealthy: TypesGen.HealthcheckReport = {
|
|||
},
|
||||
database: {
|
||||
healthy: false,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
latency: "",
|
||||
latency_ms: 0,
|
||||
|
@ -2845,12 +2865,14 @@ export const DeploymentHealthUnhealthy: TypesGen.HealthcheckReport = {
|
|||
},
|
||||
derp: {
|
||||
healthy: false,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
regions: [],
|
||||
netcheck_logs: [],
|
||||
},
|
||||
websocket: {
|
||||
healthy: false,
|
||||
severity: "ok",
|
||||
warnings: [],
|
||||
body: "",
|
||||
code: 0,
|
||||
|
|
Loading…
Reference in New Issue