feat: include health severity in reports (#10817)

This commit is contained in:
Marcin Tojek 2023-11-23 16:08:41 +01:00 committed by GitHub
parent e311e9ec24
commit 78df68348a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 542 additions and 91 deletions

106
coderd/apidoc/docs.go generated
View File

@ -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": {

View File

@ -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": {

View File

@ -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)
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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
}
}

View File

@ -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)

View File

@ -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.

View File

@ -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]
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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)
})

9
docs/api/debug.md generated
View File

@ -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"]
}
}

231
docs/api/schemas.md generated
View File

@ -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

View File

@ -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 = " "
)

View File

@ -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"

View File

@ -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,