From 205a224ac9e7fc26388dcbcf8dd31da6dc36039c Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 21 Nov 2023 13:57:11 +0100 Subject: [PATCH 01/18] Severity definition --- coderd/apidoc/docs.go | 25 +++++++++++++++++++++++++ coderd/apidoc/swagger.json | 13 +++++++++++++ coderd/healthcheck/derphealth/derp.go | 10 ++++++++++ docs/api/debug.md | 1 + docs/api/schemas.md | 27 +++++++++++++++++++++++++++ site/src/api/typesGenerated.ts | 9 +++++++++ 6 files changed, 85 insertions(+) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 74537c0f7a483..11329b312cd0c 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -12184,6 +12184,18 @@ const docTemplate = `{ "$ref": "#/definitions/derphealth.RegionReport" } }, + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/derphealth.Severity" + } + ] + }, "warnings": { "type": "array", "items": { @@ -12192,6 +12204,19 @@ const docTemplate = `{ } } }, + "derphealth.Severity": { + "type": "string", + "enum": [ + "ok", + "warning", + "error" + ], + "x-enum-varnames": [ + "SeverityOK", + "SeverityWarning", + "SeverityError" + ] + }, "derphealth.StunReport": { "type": "object", "properties": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index cf00313832900..5fe0ec95e13da 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -11105,6 +11105,14 @@ "$ref": "#/definitions/derphealth.RegionReport" } }, + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/derphealth.Severity" + } + ] + }, "warnings": { "type": "array", "items": { @@ -11113,6 +11121,11 @@ } } }, + "derphealth.Severity": { + "type": "string", + "enum": ["ok", "warning", "error"], + "x-enum-varnames": ["SeverityOK", "SeverityWarning", "SeverityError"] + }, "derphealth.StunReport": { "type": "object", "properties": { diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index 8f99ca5994d25..121fcaf5201ca 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -29,9 +29,19 @@ const ( oneNodeUnhealthy = "Region is operational, but performance might be degraded as one node is unhealthy." ) +const ( + SeverityOK Severity = "ok" + SeverityWarning Severity = "warning" + SeverityError Severity = "error" +) + +// @typescript-generate Severity +type Severity string + // @typescript-generate Report type Report struct { Healthy bool `json:"healthy"` + Severity Severity `json:"severity" enums:"ok,warning,error"` Warnings []string `json:"warnings"` Regions map[int]*RegionReport `json:"regions"` diff --git a/docs/api/debug.md b/docs/api/debug.md index 521f6b766c7de..a97391c16769d 100644 --- a/docs/api/debug.md +++ b/docs/api/debug.md @@ -233,6 +233,7 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \ "warnings": ["string"] } }, + "severity": "ok", "warnings": ["string"] }, "failing_sections": ["string"], diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 544403777a085..9d478ffa46683 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -7419,6 +7419,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "warnings": ["string"] } }, + "severity": "ok", "warnings": ["string"] } ``` @@ -7434,8 +7435,33 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `netcheck_logs` | array of string | false | | | | `regions` | object | false | | | | » `[any property]` | [derphealth.RegionReport](#derphealthregionreport) | false | | | +| `severity` | [derphealth.Severity](#derphealthseverity) | false | | | | `warnings` | array of string | false | | | +#### Enumerated Values + +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | + +## derphealth.Severity + +```json +"ok" +``` + +### Properties + +#### Enumerated Values + +| Value | +| --------- | +| `ok` | +| `warning` | +| `error` | + ## derphealth.StunReport ```json @@ -7698,6 +7724,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "warnings": ["string"] } }, + "severity": "ok", "warnings": ["string"] }, "failing_sections": ["string"], diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 51921fd729e26..57581541e220a 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -2205,6 +2205,7 @@ export interface DerphealthRegionReport { // From derphealth/derp.go export interface DerphealthReport { readonly healthy: boolean; + readonly severity: DerphealthSeverity; readonly warnings: string[]; readonly regions: Record; // Named type "tailscale.com/net/netcheck.Report" unknown, using "any" @@ -2221,3 +2222,11 @@ export interface DerphealthStunReport { readonly CanSTUN: boolean; readonly Error?: string; } + +// From derphealth/derp.go +export type DerphealthSeverity = "error" | "ok" | "warning"; +export const DerphealthSeveritys: DerphealthSeverity[] = [ + "error", + "ok", + "warning", +]; From 1bcad04019d0f8728b8d4400b4088ad89832500f Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 21 Nov 2023 14:12:59 +0100 Subject: [PATCH 02/18] WIP --- coderd/healthcheck/accessurl.go | 1 + coderd/healthcheck/database.go | 1 + coderd/healthcheck/derphealth/derp.go | 5 ++++- coderd/healthcheck/healthcheck.go | 10 ++++++++++ coderd/healthcheck/websocket.go | 1 + 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/coderd/healthcheck/accessurl.go b/coderd/healthcheck/accessurl.go index fcab27463e3a8..18806a79421a4 100644 --- a/coderd/healthcheck/accessurl.go +++ b/coderd/healthcheck/accessurl.go @@ -15,6 +15,7 @@ import ( // @typescript-generate AccessURLReport type AccessURLReport struct { Healthy bool `json:"healthy"` + Severity Severity `json:"severity" enums:"ok,warning,error"` Warnings []string `json:"warnings"` AccessURL string `json:"access_url"` diff --git a/coderd/healthcheck/database.go b/coderd/healthcheck/database.go index f8a0d655796df..57254f4cef840 100644 --- a/coderd/healthcheck/database.go +++ b/coderd/healthcheck/database.go @@ -17,6 +17,7 @@ const ( // @typescript-generate DatabaseReport type DatabaseReport struct { Healthy bool `json:"healthy"` + Severity Severity `json:"severity" enums:"ok,warning,error"` Warnings []string `json:"warnings"` Reachable bool `json:"reachable"` diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index 121fcaf5201ca..582ad449739c1 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -55,8 +55,10 @@ type Report struct { // @typescript-generate RegionReport type RegionReport struct { - mu sync.Mutex + mu sync.Mutex + Healthy bool `json:"healthy"` + Severity Severity `json:"severity" enums:"ok,warning,error"` Warnings []string `json:"warnings"` Region *tailcfg.DERPRegion `json:"region"` @@ -70,6 +72,7 @@ type NodeReport struct { clientCounter int Healthy bool `json:"healthy"` + Severity Severity `json:"severity" enums:"ok,warning,error"` Warnings []string `json:"warnings"` Node *tailcfg.DERPNode `json:"node"` diff --git a/coderd/healthcheck/healthcheck.go b/coderd/healthcheck/healthcheck.go index 80af31ae524a2..f82fd0fe5a3e3 100644 --- a/coderd/healthcheck/healthcheck.go +++ b/coderd/healthcheck/healthcheck.go @@ -18,6 +18,14 @@ const ( SectionDatabase string = "Database" ) +const ( + SeverityOK Severity = "ok" + SeverityWarning Severity = "warning" + SeverityError Severity = "error" +) + +type Severity string + type Checker interface { DERP(ctx context.Context, opts *derphealth.ReportOptions) derphealth.Report AccessURL(ctx context.Context, opts *AccessURLReportOptions) AccessURLReport @@ -31,6 +39,8 @@ type Report struct { Time time.Time `json:"time"` // Healthy is true if the report returns no errors. Healthy bool `json:"healthy"` + // Severity indicates the status of Coder health. + Severity Severity `json:"severity" enums:"ok,warning,error"` // FailingSections is a list of sections that have failed their healthcheck. FailingSections []string `json:"failing_sections"` diff --git a/coderd/healthcheck/websocket.go b/coderd/healthcheck/websocket.go index 46ce08f0f1bdd..6b6e218f7a0ea 100644 --- a/coderd/healthcheck/websocket.go +++ b/coderd/healthcheck/websocket.go @@ -22,6 +22,7 @@ type WebsocketReportOptions struct { // @typescript-generate WebsocketReport type WebsocketReport struct { Healthy bool `json:"healthy"` + Severity Severity `json:"severity" enums:"ok,warning,error"` Warnings []string `json:"warnings"` Body string `json:"body"` From e1978f22c240d6e0785730be806d82b0084400a5 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 21 Nov 2023 15:16:29 +0100 Subject: [PATCH 03/18] use model struct for base health summary --- coderd/healthcheck/accessurl.go | 5 ++--- coderd/healthcheck/database.go | 5 ++--- coderd/healthcheck/derphealth/derp.go | 28 ++++++++------------------- coderd/healthcheck/derphealth/doc.go | 5 +++++ coderd/healthcheck/healthcheck.go | 11 ++--------- coderd/healthcheck/model/base.go | 16 +++++++++++++++ coderd/healthcheck/websocket.go | 6 +++--- 7 files changed, 38 insertions(+), 38 deletions(-) create mode 100644 coderd/healthcheck/derphealth/doc.go create mode 100644 coderd/healthcheck/model/base.go diff --git a/coderd/healthcheck/accessurl.go b/coderd/healthcheck/accessurl.go index 18806a79421a4..fbf848d8d1062 100644 --- a/coderd/healthcheck/accessurl.go +++ b/coderd/healthcheck/accessurl.go @@ -9,14 +9,13 @@ import ( "golang.org/x/xerrors" + "github.com/coder/coder/v2/coderd/healthcheck/model" "github.com/coder/coder/v2/coderd/util/ptr" ) // @typescript-generate AccessURLReport type AccessURLReport struct { - Healthy bool `json:"healthy"` - Severity Severity `json:"severity" enums:"ok,warning,error"` - Warnings []string `json:"warnings"` + model.HealthSummary AccessURL string `json:"access_url"` Reachable bool `json:"reachable"` diff --git a/coderd/healthcheck/database.go b/coderd/healthcheck/database.go index 57254f4cef840..b24f8491313d6 100644 --- a/coderd/healthcheck/database.go +++ b/coderd/healthcheck/database.go @@ -8,6 +8,7 @@ import ( "golang.org/x/xerrors" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/healthcheck/model" ) const ( @@ -16,9 +17,7 @@ const ( // @typescript-generate DatabaseReport type DatabaseReport struct { - Healthy bool `json:"healthy"` - Severity Severity `json:"severity" enums:"ok,warning,error"` - Warnings []string `json:"warnings"` + model.HealthSummary Reachable bool `json:"reachable"` Latency string `json:"latency"` diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index 582ad449739c1..076a00ab09a13 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -21,6 +21,7 @@ import ( "tailscale.com/types/key" tslogger "tailscale.com/types/logger" + "github.com/coder/coder/v2/coderd/healthcheck/model" "github.com/coder/coder/v2/coderd/util/ptr" ) @@ -29,20 +30,9 @@ const ( oneNodeUnhealthy = "Region is operational, but performance might be degraded as one node is unhealthy." ) -const ( - SeverityOK Severity = "ok" - SeverityWarning Severity = "warning" - SeverityError Severity = "error" -) - -// @typescript-generate Severity -type Severity string - // @typescript-generate Report type Report struct { - Healthy bool `json:"healthy"` - Severity Severity `json:"severity" enums:"ok,warning,error"` - Warnings []string `json:"warnings"` + model.HealthSummary Regions map[int]*RegionReport `json:"regions"` @@ -57,9 +47,7 @@ type Report struct { type RegionReport struct { mu sync.Mutex - Healthy bool `json:"healthy"` - Severity Severity `json:"severity" enums:"ok,warning,error"` - Warnings []string `json:"warnings"` + model.HealthSummary Region *tailcfg.DERPRegion `json:"region"` NodeReports []*NodeReport `json:"node_reports"` @@ -71,9 +59,7 @@ type NodeReport struct { mu sync.Mutex clientCounter int - Healthy bool `json:"healthy"` - Severity Severity `json:"severity" enums:"ok,warning,error"` - Warnings []string `json:"warnings"` + model.HealthSummary Node *tailcfg.DERPNode `json:"node"` @@ -167,8 +153,10 @@ func (r *RegionReport) Run(ctx context.Context) { var ( node = node nodeReport = NodeReport{ - Node: node, - Healthy: true, + Node: node, + HealthSummary: model.HealthSummary{ + Healthy: true, + }, } ) diff --git a/coderd/healthcheck/derphealth/doc.go b/coderd/healthcheck/derphealth/doc.go new file mode 100644 index 0000000000000..9a02a0395cca6 --- /dev/null +++ b/coderd/healthcheck/derphealth/doc.go @@ -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. diff --git a/coderd/healthcheck/healthcheck.go b/coderd/healthcheck/healthcheck.go index f82fd0fe5a3e3..f170b75be77e2 100644 --- a/coderd/healthcheck/healthcheck.go +++ b/coderd/healthcheck/healthcheck.go @@ -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/model" "github.com/coder/coder/v2/coderd/util/ptr" ) @@ -18,14 +19,6 @@ const ( SectionDatabase string = "Database" ) -const ( - SeverityOK Severity = "ok" - SeverityWarning Severity = "warning" - SeverityError Severity = "error" -) - -type Severity string - type Checker interface { DERP(ctx context.Context, opts *derphealth.ReportOptions) derphealth.Report AccessURL(ctx context.Context, opts *AccessURLReportOptions) AccessURLReport @@ -40,7 +33,7 @@ type Report struct { // Healthy is true if the report returns no errors. Healthy bool `json:"healthy"` // Severity indicates the status of Coder health. - Severity Severity `json:"severity" enums:"ok,warning,error"` + Severity model.Severity `json:"severity" enums:"ok,warning,error"` // FailingSections is a list of sections that have failed their healthcheck. FailingSections []string `json:"failing_sections"` diff --git a/coderd/healthcheck/model/base.go b/coderd/healthcheck/model/base.go new file mode 100644 index 0000000000000..418904e135aaf --- /dev/null +++ b/coderd/healthcheck/model/base.go @@ -0,0 +1,16 @@ +package model + +const ( + SeverityOK Severity = "ok" + SeverityWarning Severity = "warning" + SeverityError Severity = "error" +) + +// @typescript-generate Severity +type Severity string + +type HealthSummary struct { + Healthy bool `json:"healthy"` + Severity Severity `json:"severity" enums:"ok,warning,error"` + Warnings []string `json:"warnings"` +} diff --git a/coderd/healthcheck/websocket.go b/coderd/healthcheck/websocket.go index 6b6e218f7a0ea..647e19ce2aa59 100644 --- a/coderd/healthcheck/websocket.go +++ b/coderd/healthcheck/websocket.go @@ -11,6 +11,8 @@ import ( "golang.org/x/xerrors" "nhooyr.io/websocket" + + "github.com/coder/coder/v2/coderd/healthcheck/model" ) type WebsocketReportOptions struct { @@ -21,9 +23,7 @@ type WebsocketReportOptions struct { // @typescript-generate WebsocketReport type WebsocketReport struct { - Healthy bool `json:"healthy"` - Severity Severity `json:"severity" enums:"ok,warning,error"` - Warnings []string `json:"warnings"` + model.HealthSummary Body string `json:"body"` Code int `json:"code"` From 0e3bc1733fff4441c61735f688c1d3dd6ede192f Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 21 Nov 2023 15:29:29 +0100 Subject: [PATCH 04/18] The best I can do --- coderd/apidoc/docs.go | 101 ++++++++++++++--- coderd/apidoc/swagger.json | 61 +++++++++- coderd/healthcheck/accessurl.go | 6 +- coderd/healthcheck/database.go | 6 +- coderd/healthcheck/derphealth/derp.go | 20 ++-- coderd/healthcheck/health/model.go | 10 ++ coderd/healthcheck/healthcheck.go | 4 +- coderd/healthcheck/model/base.go | 16 --- coderd/healthcheck/websocket.go | 7 +- docs/api/debug.md | 8 ++ docs/api/schemas.md | 156 +++++++++++++++++++------- scripts/apitypings/main.go | 2 +- site/src/api/typesGenerated.ts | 25 +++-- 13 files changed, 317 insertions(+), 105 deletions(-) create mode 100644 coderd/healthcheck/health/model.go delete mode 100644 coderd/healthcheck/model/base.go diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 11329b312cd0c..36bc40f06c2d3 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -12117,6 +12117,18 @@ const docTemplate = `{ "round_trip_ping_ms": { "type": "integer" }, + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, "stun": { "$ref": "#/definitions/derphealth.StunReport" }, @@ -12149,6 +12161,18 @@ const docTemplate = `{ "region": { "$ref": "#/definitions/tailcfg.DERPRegion" }, + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, "warnings": { "type": "array", "items": { @@ -12192,7 +12216,7 @@ const docTemplate = `{ ], "allOf": [ { - "$ref": "#/definitions/derphealth.Severity" + "$ref": "#/definitions/health.Severity" } ] }, @@ -12204,19 +12228,6 @@ const docTemplate = `{ } } }, - "derphealth.Severity": { - "type": "string", - "enum": [ - "ok", - "warning", - "error" - ], - "x-enum-varnames": [ - "SeverityOK", - "SeverityWarning", - "SeverityError" - ] - }, "derphealth.StunReport": { "type": "object", "properties": { @@ -12231,6 +12242,19 @@ const docTemplate = `{ } } }, + "health.Severity": { + "type": "string", + "enum": [ + "ok", + "warning", + "error" + ], + "x-enum-varnames": [ + "SeverityOK", + "SeverityWarning", + "SeverityError" + ] + }, "healthcheck.AccessURLReport": { "type": "object", "properties": { @@ -12249,6 +12273,18 @@ const docTemplate = `{ "reachable": { "type": "boolean" }, + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, "status_code": { "type": "integer" }, @@ -12278,6 +12314,18 @@ const docTemplate = `{ "reachable": { "type": "boolean" }, + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, "threshold_ms": { "type": "integer" }, @@ -12316,6 +12364,19 @@ const docTemplate = `{ "description": "Healthy is true if the report returns no errors.", "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" @@ -12340,6 +12401,18 @@ const docTemplate = `{ "healthy": { "type": "boolean" }, + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, "warnings": { "type": "array", "items": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 5fe0ec95e13da..bc47fce47207d 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -11038,6 +11038,14 @@ "round_trip_ping_ms": { "type": "integer" }, + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, "stun": { "$ref": "#/definitions/derphealth.StunReport" }, @@ -11070,6 +11078,14 @@ "region": { "$ref": "#/definitions/tailcfg.DERPRegion" }, + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, "warnings": { "type": "array", "items": { @@ -11109,7 +11125,7 @@ "enum": ["ok", "warning", "error"], "allOf": [ { - "$ref": "#/definitions/derphealth.Severity" + "$ref": "#/definitions/health.Severity" } ] }, @@ -11121,11 +11137,6 @@ } } }, - "derphealth.Severity": { - "type": "string", - "enum": ["ok", "warning", "error"], - "x-enum-varnames": ["SeverityOK", "SeverityWarning", "SeverityError"] - }, "derphealth.StunReport": { "type": "object", "properties": { @@ -11140,6 +11151,11 @@ } } }, + "health.Severity": { + "type": "string", + "enum": ["ok", "warning", "error"], + "x-enum-varnames": ["SeverityOK", "SeverityWarning", "SeverityError"] + }, "healthcheck.AccessURLReport": { "type": "object", "properties": { @@ -11158,6 +11174,14 @@ "reachable": { "type": "boolean" }, + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, "status_code": { "type": "integer" }, @@ -11187,6 +11211,14 @@ "reachable": { "type": "boolean" }, + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, "threshold_ms": { "type": "integer" }, @@ -11225,6 +11257,15 @@ "description": "Healthy is true if the report returns no errors.", "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" @@ -11249,6 +11290,14 @@ "healthy": { "type": "boolean" }, + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, "warnings": { "type": "array", "items": { diff --git a/coderd/healthcheck/accessurl.go b/coderd/healthcheck/accessurl.go index fbf848d8d1062..0ba62854c0c14 100644 --- a/coderd/healthcheck/accessurl.go +++ b/coderd/healthcheck/accessurl.go @@ -9,13 +9,15 @@ import ( "golang.org/x/xerrors" - "github.com/coder/coder/v2/coderd/healthcheck/model" + "github.com/coder/coder/v2/coderd/healthcheck/health" "github.com/coder/coder/v2/coderd/util/ptr" ) // @typescript-generate AccessURLReport type AccessURLReport struct { - model.HealthSummary + 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"` diff --git a/coderd/healthcheck/database.go b/coderd/healthcheck/database.go index b24f8491313d6..2ab5f44bd3a22 100644 --- a/coderd/healthcheck/database.go +++ b/coderd/healthcheck/database.go @@ -8,7 +8,7 @@ import ( "golang.org/x/xerrors" "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/healthcheck/model" + "github.com/coder/coder/v2/coderd/healthcheck/health" ) const ( @@ -17,7 +17,9 @@ const ( // @typescript-generate DatabaseReport type DatabaseReport struct { - model.HealthSummary + 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"` diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index 076a00ab09a13..93eb5fd46b0d1 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -21,7 +21,7 @@ import ( "tailscale.com/types/key" tslogger "tailscale.com/types/logger" - "github.com/coder/coder/v2/coderd/healthcheck/model" + "github.com/coder/coder/v2/coderd/healthcheck/health" "github.com/coder/coder/v2/coderd/util/ptr" ) @@ -32,7 +32,9 @@ const ( // @typescript-generate Report type Report struct { - model.HealthSummary + Healthy bool `json:"healthy"` + Severity health.Severity `json:"severity" enums:"ok,warning,error"` + Warnings []string `json:"warnings"` Regions map[int]*RegionReport `json:"regions"` @@ -47,7 +49,9 @@ type Report struct { type RegionReport struct { mu sync.Mutex - model.HealthSummary + 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"` @@ -59,7 +63,9 @@ type NodeReport struct { mu sync.Mutex clientCounter int - model.HealthSummary + Healthy bool `json:"healthy"` + Severity health.Severity `json:"severity" enums:"ok,warning,error"` + Warnings []string `json:"warnings"` Node *tailcfg.DERPNode `json:"node"` @@ -153,10 +159,8 @@ func (r *RegionReport) Run(ctx context.Context) { var ( node = node nodeReport = NodeReport{ - Node: node, - HealthSummary: model.HealthSummary{ - Healthy: true, - }, + Node: node, + Healthy: true, } ) diff --git a/coderd/healthcheck/health/model.go b/coderd/healthcheck/health/model.go new file mode 100644 index 0000000000000..61e30b7720dab --- /dev/null +++ b/coderd/healthcheck/health/model.go @@ -0,0 +1,10 @@ +package health + +const ( + SeverityOK Severity = "ok" + SeverityWarning Severity = "warning" + SeverityError Severity = "error" +) + +// @typescript-generate Severity +type Severity string diff --git a/coderd/healthcheck/healthcheck.go b/coderd/healthcheck/healthcheck.go index f170b75be77e2..0b8f8ced751f5 100644 --- a/coderd/healthcheck/healthcheck.go +++ b/coderd/healthcheck/healthcheck.go @@ -8,7 +8,7 @@ import ( "github.com/coder/coder/v2/buildinfo" "github.com/coder/coder/v2/coderd/healthcheck/derphealth" - "github.com/coder/coder/v2/coderd/healthcheck/model" + "github.com/coder/coder/v2/coderd/healthcheck/health" "github.com/coder/coder/v2/coderd/util/ptr" ) @@ -33,7 +33,7 @@ type Report struct { // Healthy is true if the report returns no errors. Healthy bool `json:"healthy"` // Severity indicates the status of Coder health. - Severity model.Severity `json:"severity" enums:"ok,warning,error"` + 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"` diff --git a/coderd/healthcheck/model/base.go b/coderd/healthcheck/model/base.go deleted file mode 100644 index 418904e135aaf..0000000000000 --- a/coderd/healthcheck/model/base.go +++ /dev/null @@ -1,16 +0,0 @@ -package model - -const ( - SeverityOK Severity = "ok" - SeverityWarning Severity = "warning" - SeverityError Severity = "error" -) - -// @typescript-generate Severity -type Severity string - -type HealthSummary struct { - Healthy bool `json:"healthy"` - Severity Severity `json:"severity" enums:"ok,warning,error"` - Warnings []string `json:"warnings"` -} diff --git a/coderd/healthcheck/websocket.go b/coderd/healthcheck/websocket.go index 647e19ce2aa59..a3e19a775e1fb 100644 --- a/coderd/healthcheck/websocket.go +++ b/coderd/healthcheck/websocket.go @@ -9,10 +9,9 @@ import ( "strconv" "time" + "github.com/coder/coder/v2/coderd/healthcheck/health" "golang.org/x/xerrors" "nhooyr.io/websocket" - - "github.com/coder/coder/v2/coderd/healthcheck/model" ) type WebsocketReportOptions struct { @@ -23,7 +22,9 @@ type WebsocketReportOptions struct { // @typescript-generate WebsocketReport type WebsocketReport struct { - model.HealthSummary + 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"` diff --git a/docs/api/debug.md b/docs/api/debug.md index a97391c16769d..c2a26321716d3 100644 --- a/docs/api/debug.md +++ b/docs/api/debug.md @@ -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,6 +235,7 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \ "regionID": 0, "regionName": "string" }, + "severity": "ok", "warnings": ["string"] } }, @@ -238,12 +244,14 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \ }, "failing_sections": ["string"], "healthy": true, + "severity": "ok", "time": "string", "websocket": { "body": "string", "code": 0, "error": "string", "healthy": true, + "severity": "ok", "warnings": ["string"] } } diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 9d478ffa46683..90e0be709d524 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -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, @@ -7162,10 +7163,19 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `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 ```json @@ -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,6 +7244,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "regionID": 0, "regionName": "string" }, + "severity": "ok", "warnings": ["string"] } ``` @@ -7245,8 +7257,17 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `healthy` | boolean | false | | | | `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 ```json @@ -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,6 +7440,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "regionID": 0, "regionName": "string" }, + "severity": "ok", "warnings": ["string"] } }, @@ -7435,7 +7460,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `netcheck_logs` | array of string | false | | | | `regions` | object | false | | | | » `[any property]` | [derphealth.RegionReport](#derphealthregionreport) | false | | | -| `severity` | [derphealth.Severity](#derphealthseverity) | false | | | +| `severity` | [health.Severity](#healthseverity) | false | | | | `warnings` | array of string | false | | | #### Enumerated Values @@ -7446,22 +7471,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `severity` | `warning` | | `severity` | `error` | -## derphealth.Severity - -```json -"ok" -``` - -### Properties - -#### Enumerated Values - -| Value | -| --------- | -| `ok` | -| `warning` | -| `error` | - ## derphealth.StunReport ```json @@ -7480,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 @@ -7489,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"] } @@ -7496,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 | | | +| `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 @@ -7515,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"] } @@ -7522,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 | | | +| `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 @@ -7542,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"] }, @@ -7552,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"] }, @@ -7622,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, @@ -7655,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": { @@ -7688,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, @@ -7721,6 +7771,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "regionID": 0, "regionName": "string" }, + "severity": "ok", "warnings": ["string"] } }, @@ -7729,12 +7780,14 @@ If the schedule is empty, the user will be updated to use the default schedule.| }, "failing_sections": ["string"], "healthy": true, + "severity": "ok", "time": "string", "websocket": { "body": "string", "code": 0, "error": "string", "healthy": true, + "severity": "ok", "warnings": ["string"] } } @@ -7750,9 +7803,18 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `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. | +| `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 ```json @@ -7761,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 | | | +| `severity` | [health.Severity](#healthseverity) | false | | | +| `warnings` | array of string | false | | | + +#### Enumerated Values + +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | ## netcheck.Report diff --git a/scripts/apitypings/main.go b/scripts/apitypings/main.go index 06b5d44e6dd3c..7db5565bf2bba 100644 --- a/scripts/apitypings/main.go +++ b/scripts/apitypings/main.go @@ -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 = " " ) diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 57581541e220a..ccf06da3f8b47 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -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,7 +2219,8 @@ export interface DerphealthRegionReport { // From derphealth/derp.go export interface DerphealthReport { readonly healthy: boolean; - readonly severity: DerphealthSeverity; + // 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; // Named type "tailscale.com/net/netcheck.Report" unknown, using "any" @@ -2222,11 +2237,3 @@ export interface DerphealthStunReport { readonly CanSTUN: boolean; readonly Error?: string; } - -// From derphealth/derp.go -export type DerphealthSeverity = "error" | "ok" | "warning"; -export const DerphealthSeveritys: DerphealthSeverity[] = [ - "error", - "ok", - "warning", -]; From f8af18c7cd7ed59a2b251e6af60965fe2d0e89bc Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 21 Nov 2023 15:31:01 +0100 Subject: [PATCH 05/18] fix imports --- coderd/healthcheck/websocket.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coderd/healthcheck/websocket.go b/coderd/healthcheck/websocket.go index a3e19a775e1fb..88dd47089b371 100644 --- a/coderd/healthcheck/websocket.go +++ b/coderd/healthcheck/websocket.go @@ -9,9 +9,10 @@ import ( "strconv" "time" - "github.com/coder/coder/v2/coderd/healthcheck/health" "golang.org/x/xerrors" "nhooyr.io/websocket" + + "github.com/coder/coder/v2/coderd/healthcheck/health" ) type WebsocketReportOptions struct { From baae5647e16ace3d8f981b189d4fd14658452443 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 21 Nov 2023 16:04:08 +0100 Subject: [PATCH 06/18] severity --- site/src/testHelpers/entities.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 9aeb3560ce961..4eb3cbc29c15f 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -2379,13 +2379,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, @@ -2412,6 +2415,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { node_reports: [ { healthy: true, + severity: "ok", warnings: [], node: { Name: "999stun0", @@ -2437,6 +2441,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { }, { healthy: true, + severity: "ok", warnings: [], node: { Name: "999b", @@ -2471,6 +2476,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { }, "10007": { healthy: true, + severity: "ok", warnings: [], region: { EmbeddedRelay: false, @@ -2497,6 +2503,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { node_reports: [ { healthy: true, + severity: "ok", warnings: [], node: { Name: "10007stun0", @@ -2522,6 +2529,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { }, { healthy: true, + severity: "ok", warnings: [], node: { Name: "10007a", @@ -2556,6 +2564,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { }, "10008": { healthy: true, + severity: "ok", warnings: [], region: { EmbeddedRelay: false, @@ -2582,6 +2591,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { node_reports: [ { healthy: true, + severity: "ok", warnings: [], node: { Name: "10008stun0", @@ -2607,6 +2617,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { }, { healthy: true, + severity: "ok", warnings: [], node: { Name: "10008a", @@ -2641,6 +2652,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { }, "10009": { healthy: true, + severity: "ok", warnings: [], region: { EmbeddedRelay: false, @@ -2667,6 +2679,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { node_reports: [ { healthy: true, + severity: "ok", warnings: [], node: { Name: "10009stun0", @@ -2692,6 +2705,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { }, { healthy: true, + severity: "ok", warnings: [], node: { Name: "10009a", @@ -2771,6 +2785,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { }, access_url: { healthy: true, + severity: "ok", warnings: [], access_url: "https://dev.coder.com", reachable: true, @@ -2779,12 +2794,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", @@ -2805,11 +2822,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: "", @@ -2818,6 +2837,7 @@ export const DeploymentHealthUnhealthy: TypesGen.HealthcheckReport = { }, database: { healthy: false, + severity: "ok", warnings: [], latency: "", latency_ms: 0, @@ -2826,12 +2846,14 @@ export const DeploymentHealthUnhealthy: TypesGen.HealthcheckReport = { }, derp: { healthy: false, + severity: "ok", warnings: [], regions: [], netcheck_logs: [], }, websocket: { healthy: false, + severity: "ok", warnings: [], body: "", code: 0, From 3d17eb5adb61b565eff7d025513d0dfcbcb88d11 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 21 Nov 2023 20:17:19 +0100 Subject: [PATCH 07/18] WIP --- coderd/healthcheck/derphealth/derp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index 93eb5fd46b0d1..405afbe314bd0 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -245,6 +245,7 @@ func (r *NodeReport) Run(ctx context.Context) { if r.UsesWebsocket { r.Warnings = append(r.Warnings, warningNodeUsesWebsocket) + // FIXME } } From 8dcbafdd5ac47fb39b169534d5d055ade31aa8f6 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 22 Nov 2023 16:39:32 +0100 Subject: [PATCH 08/18] WIP --- coderd/healthcheck/database.go | 7 +++++-- coderd/healthcheck/database_test.go | 2 ++ coderd/healthcheck/websocket.go | 7 +++++++ coderd/healthcheck/websocket_test.go | 2 ++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/coderd/healthcheck/database.go b/coderd/healthcheck/database.go index 2ab5f44bd3a22..268ff8184f668 100644 --- a/coderd/healthcheck/database.go +++ b/coderd/healthcheck/database.go @@ -35,6 +35,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() @@ -49,6 +50,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) @@ -59,8 +61,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 } diff --git a/coderd/healthcheck/database_test.go b/coderd/healthcheck/database_test.go index be97c6424a47a..c67999b6a3f2c 100644 --- a/coderd/healthcheck/database_test.go +++ b/coderd/healthcheck/database_test.go @@ -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) diff --git a/coderd/healthcheck/websocket.go b/coderd/healthcheck/websocket.go index 88dd47089b371..0bdeaad7fae67 100644 --- a/coderd/healthcheck/websocket.go +++ b/coderd/healthcheck/websocket.go @@ -36,10 +36,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" { @@ -67,6 +69,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") @@ -76,22 +79,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 } } diff --git a/coderd/healthcheck/websocket_test.go b/coderd/healthcheck/websocket_test.go index 44df237a49cbb..861e1acfd07fa 100644 --- a/coderd/healthcheck/websocket_test.go +++ b/coderd/healthcheck/websocket_test.go @@ -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) }) From d00802e3867af327f8ea94af6a6bc5075ee88796 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 22 Nov 2023 17:45:11 +0100 Subject: [PATCH 09/18] more rules --- coderd/healthcheck/accessurl.go | 9 +++++++++ coderd/healthcheck/accessurl_test.go | 4 ++++ coderd/healthcheck/database_test.go | 3 +++ 3 files changed, 16 insertions(+) diff --git a/coderd/healthcheck/accessurl.go b/coderd/healthcheck/accessurl.go index 0ba62854c0c14..08c664567b07e 100644 --- a/coderd/healthcheck/accessurl.go +++ b/coderd/healthcheck/accessurl.go @@ -35,9 +35,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() @@ -49,18 +51,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() @@ -68,11 +73,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) } diff --git a/coderd/healthcheck/accessurl_test.go b/coderd/healthcheck/accessurl_test.go index 3464030b61eb1..43e839c853b96 100644 --- a/coderd/healthcheck/accessurl_test.go +++ b/coderd/healthcheck/accessurl_test.go @@ -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) diff --git a/coderd/healthcheck/database_test.go b/coderd/healthcheck/database_test.go index c67999b6a3f2c..439c4236e75ce 100644 --- a/coderd/healthcheck/database_test.go +++ b/coderd/healthcheck/database_test.go @@ -60,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) @@ -86,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) @@ -112,6 +114,7 @@ func TestDatabase(t *testing.T) { assert.False(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) From 7b3945a2a88f092949c90624b741ccca79ea7fb8 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 22 Nov 2023 18:02:14 +0100 Subject: [PATCH 10/18] Fix: database --- coderd/healthcheck/database_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/healthcheck/database_test.go b/coderd/healthcheck/database_test.go index 439c4236e75ce..a0d817e59606f 100644 --- a/coderd/healthcheck/database_test.go +++ b/coderd/healthcheck/database_test.go @@ -112,7 +112,7 @@ 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) From af2d0dc2f50318de50db0dc67ef8f7a8032bb42d Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 22 Nov 2023 18:27:39 +0100 Subject: [PATCH 11/18] WIP --- coderd/apidoc/docs.go | 6 ++ coderd/apidoc/swagger.json | 6 ++ coderd/healthcheck/accessurl.go | 1 + coderd/healthcheck/database.go | 1 + coderd/healthcheck/derphealth/derp.go | 8 +- coderd/healthcheck/websocket.go | 1 + docs/api/schemas.md | 124 +++++++++++++------------- 7 files changed, 84 insertions(+), 63 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 36bc40f06c2d3..7320aafe88cd1 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -12103,6 +12103,7 @@ const docTemplate = `{ "type": "string" }, "healthy": { + "description": "Healthy is left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, "node": { @@ -12150,6 +12151,7 @@ const docTemplate = `{ "type": "string" }, "healthy": { + "description": "Healthy is left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, "node_reports": { @@ -12188,6 +12190,7 @@ const docTemplate = `{ "type": "string" }, "healthy": { + "description": "Healthy is left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, "netcheck": { @@ -12265,6 +12268,7 @@ const docTemplate = `{ "type": "string" }, "healthy": { + "description": "Healthy is left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, "healthz_response": { @@ -12303,6 +12307,7 @@ const docTemplate = `{ "type": "string" }, "healthy": { + "description": "Healthy is left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, "latency": { @@ -12399,6 +12404,7 @@ const docTemplate = `{ "type": "string" }, "healthy": { + "description": "Healthy is left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, "severity": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index bc47fce47207d..fbe25e6879600 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -11024,6 +11024,7 @@ "type": "string" }, "healthy": { + "description": "Healthy is left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, "node": { @@ -11067,6 +11068,7 @@ "type": "string" }, "healthy": { + "description": "Healthy is left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, "node_reports": { @@ -11101,6 +11103,7 @@ "type": "string" }, "healthy": { + "description": "Healthy is left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, "netcheck": { @@ -11166,6 +11169,7 @@ "type": "string" }, "healthy": { + "description": "Healthy is left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, "healthz_response": { @@ -11200,6 +11204,7 @@ "type": "string" }, "healthy": { + "description": "Healthy is left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, "latency": { @@ -11288,6 +11293,7 @@ "type": "string" }, "healthy": { + "description": "Healthy is left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, "severity": { diff --git a/coderd/healthcheck/accessurl.go b/coderd/healthcheck/accessurl.go index 08c664567b07e..c00611ad5ce58 100644 --- a/coderd/healthcheck/accessurl.go +++ b/coderd/healthcheck/accessurl.go @@ -15,6 +15,7 @@ import ( // @typescript-generate AccessURLReport type AccessURLReport struct { + // Healthy is 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"` diff --git a/coderd/healthcheck/database.go b/coderd/healthcheck/database.go index 268ff8184f668..24bd81f278a5e 100644 --- a/coderd/healthcheck/database.go +++ b/coderd/healthcheck/database.go @@ -17,6 +17,7 @@ const ( // @typescript-generate DatabaseReport type DatabaseReport struct { + // Healthy is 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"` diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index 48629e23834f6..17a3f336b90f6 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -34,6 +34,7 @@ const ( // @typescript-generate Report type Report struct { + // Healthy is 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"` @@ -51,6 +52,7 @@ type Report struct { type RegionReport struct { mu sync.Mutex + // Healthy is 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"` @@ -65,6 +67,7 @@ type NodeReport struct { mu sync.Mutex clientCounter int + // Healthy is 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"` @@ -171,6 +174,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 } }() @@ -226,6 +230,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{} @@ -248,11 +253,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) - // FIXME + r.Severity = health.SeverityWarning } } diff --git a/coderd/healthcheck/websocket.go b/coderd/healthcheck/websocket.go index 0bdeaad7fae67..13488229a3f60 100644 --- a/coderd/healthcheck/websocket.go +++ b/coderd/healthcheck/websocket.go @@ -23,6 +23,7 @@ type WebsocketReportOptions struct { // @typescript-generate WebsocketReport type WebsocketReport struct { + // Healthy is 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"` diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 90e0be709d524..437ec33a285bd 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -7152,21 +7152,21 @@ 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 | | | -| `severity` | [health.Severity](#healthseverity) | 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 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 @@ -7251,14 +7251,14 @@ 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 | | | -| `node_reports` | array of [derphealth.NodeReport](#derphealthnodereport) | false | | | -| `region` | [tailcfg.DERPRegion](#tailcfgderpregion) | false | | | -| `severity` | [health.Severity](#healthseverity) | false | | | -| `warnings` | array of string | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------- | +| `error` | string | false | | | +| `healthy` | boolean | false | | Healthy is 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 @@ -7451,17 +7451,17 @@ 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 | | | -| `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 | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | -------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------- | +| `error` | string | false | | | +| `healthy` | boolean | false | | Healthy is 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 @@ -7522,16 +7522,16 @@ 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 | | | -| `severity` | [health.Severity](#healthseverity) | 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 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 @@ -7558,16 +7558,16 @@ 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 | | | -| `severity` | [health.Severity](#healthseverity) | false | | | -| `threshold_ms` | integer | false | | | -| `warnings` | array of string | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | ---------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------- | +| `error` | string | false | | | +| `healthy` | boolean | false | | Healthy is 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 @@ -7830,14 +7830,14 @@ If the schedule is empty, the user will be updated to use the default schedule.| ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------- | ---------------------------------- | -------- | ------------ | ----------- | -| `body` | string | false | | | -| `code` | integer | false | | | -| `error` | string | false | | | -| `healthy` | boolean | false | | | -| `severity` | [health.Severity](#healthseverity) | 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 left for backward compatibility purposes, use `Severity` instead. | +| `severity` | [health.Severity](#healthseverity) | false | | | +| `warnings` | array of string | false | | | #### Enumerated Values From 6c59dc3b7f6fe1ede9246a1c8f1f36b09435db53 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 22 Nov 2023 19:16:27 +0100 Subject: [PATCH 12/18] Reports --- coderd/healthcheck/derphealth/derp.go | 8 ++++++++ coderd/healthcheck/health/model.go | 10 ++++++++++ coderd/healthcheck/healthcheck.go | 16 ++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index 17a3f336b90f6..b8b0350377e15 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -154,6 +154,7 @@ func (r *Report) Run(ctx context.Context, opts *ReportOptions) { func (r *RegionReport) Run(ctx context.Context) { r.Healthy = true + r.Severity = health.SeverityOK r.NodeReports = []*NodeReport{} wg := &sync.WaitGroup{} @@ -205,6 +206,13 @@ func (r *RegionReport) Run(ctx context.Context) { } else if healthyNodes < len(r.Region.Nodes) { r.Warnings = append(r.Warnings, oneNodeUnhealthy) } + + // Review node reports and select the highest severing. + for _, nodeReport := range r.NodeReports { + if nodeReport.Severity.Value() > r.Severity.Value() { + r.Severity = nodeReport.Severity + } + } } func (r *NodeReport) derpURL() *url.URL { diff --git a/coderd/healthcheck/health/model.go b/coderd/healthcheck/health/model.go index 61e30b7720dab..461c9c8f3c1a1 100644 --- a/coderd/healthcheck/health/model.go +++ b/coderd/healthcheck/health/model.go @@ -8,3 +8,13 @@ const ( // @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] +} diff --git a/coderd/healthcheck/healthcheck.go b/coderd/healthcheck/healthcheck.go index 0b8f8ced751f5..824a29634fa5f 100644 --- a/coderd/healthcheck/healthcheck.go +++ b/coderd/healthcheck/healthcheck.go @@ -154,6 +154,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 } From aba2a9e620ea9bd6bf47b6aabf748f7a56293a3d Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 22 Nov 2023 19:43:42 +0100 Subject: [PATCH 13/18] DERP servers partially unhealthy --- coderd/healthcheck/derphealth/derp.go | 26 +++++++++++++++++----- coderd/healthcheck/derphealth/derp_test.go | 4 ++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index b8b0350377e15..1c43735d24056 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -99,6 +99,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{} @@ -150,6 +152,13 @@ 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) { @@ -203,14 +212,19 @@ func (r *RegionReport) Run(ctx context.Context) { // Coder allows for 1 unhealthy node in the region, unless there is only 1 node. if len(r.Region.Nodes) == 1 { r.Healthy = healthyNodes == len(r.Region.Nodes) - } else if healthyNodes < len(r.Region.Nodes) { + r.Severity = r.NodeReports[0].Severity + } else if healthyNodes+1 == len(r.Region.Nodes) { + // r.Healthy = true (by default) + r.Severity = health.SeverityWarning r.Warnings = append(r.Warnings, oneNodeUnhealthy) - } + } else if healthyNodes+1 < len(r.Region.Nodes) { + r.Healthy = false - // Review node reports and select the highest severing. - for _, nodeReport := range r.NodeReports { - if nodeReport.Severity.Value() > r.Severity.Value() { - r.Severity = nodeReport.Severity + // Review node reports and select the highest severity. + for _, nodeReport := range r.NodeReports { + if nodeReport.Severity.Value() > r.Severity.Value() { + r.Severity = nodeReport.Severity + } } } } diff --git a/coderd/healthcheck/derphealth/derp_test.go b/coderd/healthcheck/derphealth/derp_test.go index a877e03fd2d57..fb1e209ee8530 100644 --- a/coderd/healthcheck/derphealth/derp_test.go +++ b/coderd/healthcheck/derphealth/derp_test.go @@ -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) } }) From c15ea21d3c716292b0acc0cfaa8030e9a89ba556 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 22 Nov 2023 19:56:58 +0100 Subject: [PATCH 14/18] warning: failover to websockets --- coderd/healthcheck/derphealth/derp_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/coderd/healthcheck/derphealth/derp_test.go b/coderd/healthcheck/derphealth/derp_test.go index fb1e209ee8530..50172581dd2f4 100644 --- a/coderd/healthcheck/derphealth/derp_test.go +++ b/coderd/healthcheck/derphealth/derp_test.go @@ -225,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) From b77763a26e4891c2a57638061fcc483d23b268e7 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 23 Nov 2023 14:34:49 +0100 Subject: [PATCH 15/18] Deprecation notice --- coderd/apidoc/docs.go | 14 +-- coderd/apidoc/swagger.json | 14 +-- coderd/healthcheck/accessurl.go | 2 +- coderd/healthcheck/database.go | 2 +- coderd/healthcheck/derphealth/derp.go | 6 +- coderd/healthcheck/healthcheck.go | 1 + coderd/healthcheck/websocket.go | 2 +- docs/api/schemas.md | 146 +++++++++++++------------- 8 files changed, 94 insertions(+), 93 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 7320aafe88cd1..ed7968577b455 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -12103,7 +12103,7 @@ const docTemplate = `{ "type": "string" }, "healthy": { - "description": "Healthy is left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, "node": { @@ -12151,7 +12151,7 @@ const docTemplate = `{ "type": "string" }, "healthy": { - "description": "Healthy is left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, "node_reports": { @@ -12190,7 +12190,7 @@ const docTemplate = `{ "type": "string" }, "healthy": { - "description": "Healthy is left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, "netcheck": { @@ -12268,7 +12268,7 @@ const docTemplate = `{ "type": "string" }, "healthy": { - "description": "Healthy is left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, "healthz_response": { @@ -12307,7 +12307,7 @@ const docTemplate = `{ "type": "string" }, "healthy": { - "description": "Healthy is left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, "latency": { @@ -12366,7 +12366,7 @@ 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": { @@ -12404,7 +12404,7 @@ const docTemplate = `{ "type": "string" }, "healthy": { - "description": "Healthy is left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, "severity": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index fbe25e6879600..f92e11b92609f 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -11024,7 +11024,7 @@ "type": "string" }, "healthy": { - "description": "Healthy is left for backward compatibility purposes, use `Severity` instead.", + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, "node": { @@ -11068,7 +11068,7 @@ "type": "string" }, "healthy": { - "description": "Healthy is left for backward compatibility purposes, use `Severity` instead.", + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, "node_reports": { @@ -11103,7 +11103,7 @@ "type": "string" }, "healthy": { - "description": "Healthy is left for backward compatibility purposes, use `Severity` instead.", + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, "netcheck": { @@ -11169,7 +11169,7 @@ "type": "string" }, "healthy": { - "description": "Healthy is left for backward compatibility purposes, use `Severity` instead.", + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, "healthz_response": { @@ -11204,7 +11204,7 @@ "type": "string" }, "healthy": { - "description": "Healthy is left for backward compatibility purposes, use `Severity` instead.", + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, "latency": { @@ -11259,7 +11259,7 @@ } }, "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": { @@ -11293,7 +11293,7 @@ "type": "string" }, "healthy": { - "description": "Healthy is left for backward compatibility purposes, use `Severity` instead.", + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, "severity": { diff --git a/coderd/healthcheck/accessurl.go b/coderd/healthcheck/accessurl.go index c00611ad5ce58..110ec3486a896 100644 --- a/coderd/healthcheck/accessurl.go +++ b/coderd/healthcheck/accessurl.go @@ -15,7 +15,7 @@ import ( // @typescript-generate AccessURLReport type AccessURLReport struct { - // Healthy is left for backward compatibility purposes, use `Severity` instead. + // 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"` diff --git a/coderd/healthcheck/database.go b/coderd/healthcheck/database.go index 24bd81f278a5e..07120ad533054 100644 --- a/coderd/healthcheck/database.go +++ b/coderd/healthcheck/database.go @@ -17,7 +17,7 @@ const ( // @typescript-generate DatabaseReport type DatabaseReport struct { - // Healthy is left for backward compatibility purposes, use `Severity` instead. + // 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"` diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index 1c43735d24056..926402cf2e467 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -34,7 +34,7 @@ const ( // @typescript-generate Report type Report struct { - // Healthy is left for backward compatibility purposes, use `Severity` instead. + // 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"` @@ -52,7 +52,7 @@ type Report struct { type RegionReport struct { mu sync.Mutex - // Healthy is left for backward compatibility purposes, use `Severity` instead. + // 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"` @@ -67,7 +67,7 @@ type NodeReport struct { mu sync.Mutex clientCounter int - // Healthy is left for backward compatibility purposes, use `Severity` instead. + // 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"` diff --git a/coderd/healthcheck/healthcheck.go b/coderd/healthcheck/healthcheck.go index 824a29634fa5f..d5498482f977e 100644 --- a/coderd/healthcheck/healthcheck.go +++ b/coderd/healthcheck/healthcheck.go @@ -31,6 +31,7 @@ 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"` diff --git a/coderd/healthcheck/websocket.go b/coderd/healthcheck/websocket.go index 13488229a3f60..c49ba1b2be10d 100644 --- a/coderd/healthcheck/websocket.go +++ b/coderd/healthcheck/websocket.go @@ -23,7 +23,7 @@ type WebsocketReportOptions struct { // @typescript-generate WebsocketReport type WebsocketReport struct { - // Healthy is left for backward compatibility purposes, use `Severity` instead. + // 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"` diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 437ec33a285bd..c4dc42883987d 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -7152,21 +7152,21 @@ 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 | | Healthy is 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 | | | +| 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 @@ -7251,14 +7251,14 @@ 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 | | Healthy is 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 | | | +| 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 @@ -7451,17 +7451,17 @@ 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 | | Healthy is 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 | | | +| 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 @@ -7522,16 +7522,16 @@ 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 | | Healthy is 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 | | | +| 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 @@ -7558,16 +7558,16 @@ 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 | | Healthy is 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 | | | +| 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 @@ -7795,17 +7795,17 @@ 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. | -| `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 | | | +| 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 @@ -7830,14 +7830,14 @@ If the schedule is empty, the user will be updated to use the default schedule.| ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------- | ---------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------- | -| `body` | string | false | | | -| `code` | integer | false | | | -| `error` | string | false | | | -| `healthy` | boolean | false | | Healthy is left for backward compatibility purposes, use `Severity` instead. | -| `severity` | [health.Severity](#healthseverity) | 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 From 5f6d7a6e6e81fe93c3f713e1e49ddc1b8e031537 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 23 Nov 2023 15:02:28 +0100 Subject: [PATCH 16/18] Switch to unhealthy --- coderd/healthcheck/derphealth/derp.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index 926402cf2e467..62914fa8a36eb 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -167,7 +167,7 @@ func (r *RegionReport) Run(ctx context.Context) { 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 { @@ -192,8 +192,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 { @@ -209,15 +209,14 @@ 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) == 1 { - r.Healthy = healthyNodes == len(r.Region.Nodes) + r.Healthy = !(r.NodeReports[0].Severity == health.SeverityError) r.Severity = r.NodeReports[0].Severity - } else if healthyNodes+1 == len(r.Region.Nodes) { + } else if unhealthyNodes == 1 { // r.Healthy = true (by default) r.Severity = health.SeverityWarning r.Warnings = append(r.Warnings, oneNodeUnhealthy) - } else if healthyNodes+1 < len(r.Region.Nodes) { + } else if unhealthyNodes > 1 { r.Healthy = false // Review node reports and select the highest severity. From 154229676781dc65266bf39f4bb68869e38a9acf Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 23 Nov 2023 15:10:11 +0100 Subject: [PATCH 17/18] simplify --- coderd/healthcheck/derphealth/derp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index 62914fa8a36eb..a2ee69cee0fe1 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -210,7 +210,7 @@ func (r *RegionReport) Run(ctx context.Context) { sortNodeReports(r.NodeReports) if len(r.Region.Nodes) == 1 { - r.Healthy = !(r.NodeReports[0].Severity == health.SeverityError) + r.Healthy = r.NodeReports[0].Severity != health.SeverityError r.Severity = r.NodeReports[0].Severity } else if unhealthyNodes == 1 { // r.Healthy = true (by default) From 140b5c6e7b4dfbef51ba0cc90f50fa3780d9e6cb Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 23 Nov 2023 15:22:43 +0100 Subject: [PATCH 18/18] Missing node report --- coderd/healthcheck/derphealth/derp.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index a2ee69cee0fe1..d6081a7f2eec8 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -30,6 +30,7 @@ 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 @@ -209,6 +210,13 @@ func (r *RegionReport) Run(ctx context.Context) { sortNodeReports(r.NodeReports) + 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 = r.NodeReports[0].Severity != health.SeverityError r.Severity = r.NodeReports[0].Severity