feat: do not fail DERP healthcheck if WebSocket is used (#10714)

This commit is contained in:
Marcin Tojek
2023-11-17 16:00:49 +01:00
committed by GitHub
parent 24aa223399
commit 8999d5785a
14 changed files with 285 additions and 80 deletions

36
coderd/apidoc/docs.go generated
View File

@ -12115,6 +12115,12 @@ const docTemplate = `{
},
"uses_websocket": {
"type": "boolean"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
@ -12135,6 +12141,12 @@ const docTemplate = `{
},
"region": {
"$ref": "#/definitions/tailcfg.DERPRegion"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
@ -12164,6 +12176,12 @@ const docTemplate = `{
"additionalProperties": {
"$ref": "#/definitions/derphealth.RegionReport"
}
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
@ -12201,6 +12219,12 @@ const docTemplate = `{
},
"status_code": {
"type": "integer"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
@ -12224,6 +12248,12 @@ const docTemplate = `{
},
"threshold_ms": {
"type": "integer"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
@ -12277,6 +12307,12 @@ const docTemplate = `{
},
"healthy": {
"type": "boolean"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},

View File

@ -11036,6 +11036,12 @@
},
"uses_websocket": {
"type": "boolean"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
@ -11056,6 +11062,12 @@
},
"region": {
"$ref": "#/definitions/tailcfg.DERPRegion"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
@ -11085,6 +11097,12 @@
"additionalProperties": {
"$ref": "#/definitions/derphealth.RegionReport"
}
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
@ -11122,6 +11140,12 @@
},
"status_code": {
"type": "integer"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
@ -11145,6 +11169,12 @@
},
"threshold_ms": {
"type": "integer"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
@ -11198,6 +11228,12 @@
},
"healthy": {
"type": "boolean"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
}
}
},

View File

@ -14,8 +14,10 @@ import (
// @typescript-generate AccessURLReport
type AccessURLReport struct {
Healthy bool `json:"healthy"`
Warnings []string `json:"warnings"`
AccessURL string `json:"access_url"`
Healthy bool `json:"healthy"`
Reachable bool `json:"reachable"`
StatusCode int `json:"status_code"`
HealthzResponse string `json:"healthz_response"`

View File

@ -16,7 +16,9 @@ const (
// @typescript-generate DatabaseReport
type DatabaseReport struct {
Healthy bool `json:"healthy"`
Healthy bool `json:"healthy"`
Warnings []string `json:"warnings"`
Reachable bool `json:"reachable"`
Latency string `json:"latency"`
LatencyMS int64 `json:"latency_ms"`

View File

@ -24,9 +24,14 @@ import (
"github.com/coder/coder/v2/coderd/util/ptr"
)
const (
warningNodeUsesWebsocket = `Node uses WebSockets because the "Upgrade: DERP" header may be blocked on the load balancer.`
)
// @typescript-generate Report
type Report struct {
Healthy bool `json:"healthy"`
Healthy bool `json:"healthy"`
Warnings []string `json:"warnings"`
Regions map[int]*RegionReport `json:"regions"`
@ -39,8 +44,9 @@ type Report struct {
// @typescript-generate RegionReport
type RegionReport struct {
mu sync.Mutex
Healthy bool `json:"healthy"`
mu sync.Mutex
Healthy bool `json:"healthy"`
Warnings []string `json:"warnings"`
Region *tailcfg.DERPRegion `json:"region"`
NodeReports []*NodeReport `json:"node_reports"`
@ -52,8 +58,10 @@ type NodeReport struct {
mu sync.Mutex
clientCounter int
Healthy bool `json:"healthy"`
Node *tailcfg.DERPNode `json:"node"`
Healthy bool `json:"healthy"`
Warnings []string `json:"warnings"`
Node *tailcfg.DERPNode `json:"node"`
ServerInfo derp.ServerInfoMessage `json:"node_info"`
CanExchangeMessages bool `json:"can_exchange_messages"`
@ -108,6 +116,10 @@ func (r *Report) Run(ctx context.Context, opts *ReportOptions) {
if !regionReport.Healthy {
r.Healthy = false
}
for _, w := range regionReport.Warnings {
r.Warnings = append(r.Warnings, fmt.Sprintf("[%s] %s", regionReport.Region.RegionName, w))
}
mu.Unlock()
}()
}
@ -159,6 +171,10 @@ func (r *RegionReport) Run(ctx context.Context) {
if !nodeReport.Healthy {
r.Healthy = false
}
for _, w := range nodeReport.Warnings {
r.Warnings = append(r.Warnings, fmt.Sprintf("[%s] %s", nodeReport.Node.Name, w))
}
r.mu.Unlock()
}()
}
@ -208,14 +224,14 @@ func (r *NodeReport) Run(ctx context.Context) {
// We can't exchange messages with the node,
if (!r.CanExchangeMessages && !r.Node.STUNOnly) ||
// A node may use websockets because `Upgrade: DERP` may be blocked on
// the load balancer. This is unhealthy because websockets are slower
// than the regular DERP protocol.
r.UsesWebsocket ||
// The node was marked as STUN compatible but the STUN test failed.
r.STUN.Error != nil {
r.Healthy = false
}
if r.UsesWebsocket {
r.Warnings = append(r.Warnings, warningNodeUsesWebsocket)
}
}
func (r *NodeReport) doExchangeMessage(ctx context.Context) {

View File

@ -170,11 +170,14 @@ func TestDERP(t *testing.T) {
report.Run(ctx, opts)
assert.False(t, report.Healthy)
assert.True(t, report.Healthy)
assert.NotEmpty(t, report.Warnings)
for _, region := range report.Regions {
assert.False(t, region.Healthy)
assert.True(t, region.Healthy)
assert.NotEmpty(t, region.Warnings)
for _, node := range region.NodeReports {
assert.False(t, node.Healthy)
assert.True(t, node.Healthy)
assert.NotEmpty(t, node.Warnings)
assert.True(t, node.CanExchangeMessages)
assert.NotEmpty(t, node.RoundTripPing)
assert.Len(t, node.ClientLogs, 2)

View File

@ -77,6 +77,25 @@ func TestHealthcheck(t *testing.T) {
},
healthy: false,
failingSections: []string{healthcheck.SectionDERP},
}, {
name: "DERPWarning",
checker: &testChecker{
DERPReport: derphealth.Report{
Healthy: true,
Warnings: []string{"foobar"},
},
AccessURLReport: healthcheck.AccessURLReport{
Healthy: true,
},
WebsocketReport: healthcheck.WebsocketReport{
Healthy: true,
},
DatabaseReport: healthcheck.DatabaseReport{
Healthy: true,
},
},
healthy: true,
failingSections: nil,
}, {
name: "AccessURLFail",
checker: &testChecker{
@ -153,6 +172,7 @@ func TestHealthcheck(t *testing.T) {
assert.Equal(t, c.healthy, report.Healthy)
assert.Equal(t, c.failingSections, report.FailingSections)
assert.Equal(t, c.checker.DERPReport.Healthy, report.DERP.Healthy)
assert.Equal(t, c.checker.DERPReport.Warnings, report.DERP.Warnings)
assert.Equal(t, c.checker.AccessURLReport.Healthy, report.AccessURL.Healthy)
assert.Equal(t, c.checker.WebsocketReport.Healthy, report.Websocket.Healthy)
assert.NotZero(t, report.Time)

View File

@ -21,10 +21,12 @@ type WebsocketReportOptions struct {
// @typescript-generate WebsocketReport
type WebsocketReport struct {
Healthy bool `json:"healthy"`
Body string `json:"body"`
Code int `json:"code"`
Error *string `json:"error"`
Healthy bool `json:"healthy"`
Warnings []string `json:"warnings"`
Body string `json:"body"`
Code int `json:"code"`
Error *string `json:"error"`
}
func (r *WebsocketReport) Run(ctx context.Context, opts *WebsocketReportOptions) {