health: begin work to use structured health warnings instead of strings, pipe changes into ipn.Notify (#12406)
Updates tailscale/tailscale#4136
This PR is the first round of work to move from encoding health warnings as strings and use structured data instead. The current health package revolves around the idea of Subsystems. Each subsystem can have (or not have) a Go error associated with it. The overall health of the backend is given by the concatenation of all these errors.
This PR polishes the concept of Warnable introduced by @bradfitz a few weeks ago. Each Warnable is a component of the backend (for instance, things like 'dns' or 'magicsock' are Warnables). Each Warnable has a unique identifying code. A Warnable is an entity we can warn the user about, by setting (or unsetting) a WarningState for it. Warnables have:
- an identifying Code, so that the GUI can track them as their WarningStates come and go
- a Title, which the GUIs can use to tell the user what component of the backend is broken
- a Text, which is a function that is called with a set of Args to generate a more detailed error message to explain the unhappy state
Additionally, this PR also begins to send Warnables and their WarningStates through LocalAPI to the clients, using ipn.Notify messages. An ipn.Notify is only issued when a warning is added or removed from the Tracker.
In a next PR, we'll get rid of subsystems entirely, and we'll start using structured warnings for all errors affecting the backend functionality.
Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
2024-06-14 18:53:56 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
package health
|
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// State contains the health status of the backend, and is
|
|
|
|
// provided to the client UI via LocalAPI through ipn.Notify.
|
|
|
|
type State struct {
|
|
|
|
// Each key-value pair in Warnings represents a Warnable that is currently
|
|
|
|
// unhealthy. If a Warnable is healthy, it will not be present in this map.
|
|
|
|
// When a Warnable is unhealthy and becomes healthy, its key-value pair
|
|
|
|
// disappears in the next issued State. Observers should treat the absence of
|
|
|
|
// a WarnableCode in this map as an indication that the Warnable became healthy,
|
|
|
|
// and may use that to clear any notifications that were previously shown to the user.
|
|
|
|
// If Warnings is nil, all Warnables are healthy and the backend is overall healthy.
|
|
|
|
Warnings map[WarnableCode]UnhealthyState
|
|
|
|
}
|
|
|
|
|
|
|
|
// Representation contains information to be shown to the user to inform them
|
|
|
|
// that a Warnable is currently unhealthy.
|
|
|
|
type UnhealthyState struct {
|
2024-07-03 20:19:06 +00:00
|
|
|
WarnableCode WarnableCode
|
|
|
|
Severity Severity
|
|
|
|
Title string
|
|
|
|
Text string
|
|
|
|
BrokenSince *time.Time `json:",omitempty"`
|
|
|
|
Args Args `json:",omitempty"`
|
|
|
|
DependsOn []WarnableCode `json:",omitempty"`
|
|
|
|
ImpactsConnectivity bool `json:",omitempty"`
|
health: begin work to use structured health warnings instead of strings, pipe changes into ipn.Notify (#12406)
Updates tailscale/tailscale#4136
This PR is the first round of work to move from encoding health warnings as strings and use structured data instead. The current health package revolves around the idea of Subsystems. Each subsystem can have (or not have) a Go error associated with it. The overall health of the backend is given by the concatenation of all these errors.
This PR polishes the concept of Warnable introduced by @bradfitz a few weeks ago. Each Warnable is a component of the backend (for instance, things like 'dns' or 'magicsock' are Warnables). Each Warnable has a unique identifying code. A Warnable is an entity we can warn the user about, by setting (or unsetting) a WarningState for it. Warnables have:
- an identifying Code, so that the GUI can track them as their WarningStates come and go
- a Title, which the GUIs can use to tell the user what component of the backend is broken
- a Text, which is a function that is called with a set of Args to generate a more detailed error message to explain the unhappy state
Additionally, this PR also begins to send Warnables and their WarningStates through LocalAPI to the clients, using ipn.Notify messages. An ipn.Notify is only issued when a warning is added or removed from the Tracker.
In a next PR, we'll get rid of subsystems entirely, and we'll start using structured warnings for all errors affecting the backend functionality.
Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
2024-06-14 18:53:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// unhealthyState returns a unhealthyState of the Warnable given its current warningState.
|
|
|
|
func (w *Warnable) unhealthyState(ws *warningState) *UnhealthyState {
|
|
|
|
var text string
|
|
|
|
if ws.Args != nil {
|
|
|
|
text = w.Text(ws.Args)
|
|
|
|
} else {
|
|
|
|
text = w.Text(Args{})
|
|
|
|
}
|
|
|
|
|
2024-06-26 05:02:38 +00:00
|
|
|
dependsOnWarnableCodes := make([]WarnableCode, len(w.DependsOn), len(w.DependsOn)+1)
|
2024-06-18 20:34:55 +00:00
|
|
|
for i, d := range w.DependsOn {
|
|
|
|
dependsOnWarnableCodes[i] = d.Code
|
|
|
|
}
|
|
|
|
|
2024-06-26 05:02:38 +00:00
|
|
|
if w != warmingUpWarnable {
|
|
|
|
// Here we tell the frontend that all Warnables depend on warmingUpWarnable. GUIs will silence all warnings until all
|
|
|
|
// their dependencies are healthy. This is a special case to prevent the GUI from showing a bunch of warnings when
|
|
|
|
// the backend is still warming up.
|
|
|
|
dependsOnWarnableCodes = append(dependsOnWarnableCodes, warmingUpWarnable.Code)
|
|
|
|
}
|
|
|
|
|
health: begin work to use structured health warnings instead of strings, pipe changes into ipn.Notify (#12406)
Updates tailscale/tailscale#4136
This PR is the first round of work to move from encoding health warnings as strings and use structured data instead. The current health package revolves around the idea of Subsystems. Each subsystem can have (or not have) a Go error associated with it. The overall health of the backend is given by the concatenation of all these errors.
This PR polishes the concept of Warnable introduced by @bradfitz a few weeks ago. Each Warnable is a component of the backend (for instance, things like 'dns' or 'magicsock' are Warnables). Each Warnable has a unique identifying code. A Warnable is an entity we can warn the user about, by setting (or unsetting) a WarningState for it. Warnables have:
- an identifying Code, so that the GUI can track them as their WarningStates come and go
- a Title, which the GUIs can use to tell the user what component of the backend is broken
- a Text, which is a function that is called with a set of Args to generate a more detailed error message to explain the unhappy state
Additionally, this PR also begins to send Warnables and their WarningStates through LocalAPI to the clients, using ipn.Notify messages. An ipn.Notify is only issued when a warning is added or removed from the Tracker.
In a next PR, we'll get rid of subsystems entirely, and we'll start using structured warnings for all errors affecting the backend functionality.
Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
2024-06-14 18:53:56 +00:00
|
|
|
return &UnhealthyState{
|
2024-07-03 20:19:06 +00:00
|
|
|
WarnableCode: w.Code,
|
|
|
|
Severity: w.Severity,
|
|
|
|
Title: w.Title,
|
|
|
|
Text: text,
|
|
|
|
BrokenSince: &ws.BrokenSince,
|
|
|
|
Args: ws.Args,
|
|
|
|
DependsOn: dependsOnWarnableCodes,
|
|
|
|
ImpactsConnectivity: w.ImpactsConnectivity,
|
health: begin work to use structured health warnings instead of strings, pipe changes into ipn.Notify (#12406)
Updates tailscale/tailscale#4136
This PR is the first round of work to move from encoding health warnings as strings and use structured data instead. The current health package revolves around the idea of Subsystems. Each subsystem can have (or not have) a Go error associated with it. The overall health of the backend is given by the concatenation of all these errors.
This PR polishes the concept of Warnable introduced by @bradfitz a few weeks ago. Each Warnable is a component of the backend (for instance, things like 'dns' or 'magicsock' are Warnables). Each Warnable has a unique identifying code. A Warnable is an entity we can warn the user about, by setting (or unsetting) a WarningState for it. Warnables have:
- an identifying Code, so that the GUI can track them as their WarningStates come and go
- a Title, which the GUIs can use to tell the user what component of the backend is broken
- a Text, which is a function that is called with a set of Args to generate a more detailed error message to explain the unhappy state
Additionally, this PR also begins to send Warnables and their WarningStates through LocalAPI to the clients, using ipn.Notify messages. An ipn.Notify is only issued when a warning is added or removed from the Tracker.
In a next PR, we'll get rid of subsystems entirely, and we'll start using structured warnings for all errors affecting the backend functionality.
Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
2024-06-14 18:53:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CurrentState returns a snapshot of the current health status of the backend.
|
|
|
|
// It returns a State with nil Warnings if the backend is healthy (all Warnables
|
|
|
|
// have no issues).
|
|
|
|
// The returned State is a snapshot of shared memory, and the caller should not
|
|
|
|
// mutate the returned value.
|
|
|
|
func (t *Tracker) CurrentState() *State {
|
|
|
|
if t.nil() {
|
|
|
|
return &State{}
|
|
|
|
}
|
|
|
|
|
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
|
|
|
|
if t.warnableVal == nil || len(t.warnableVal) == 0 {
|
|
|
|
return &State{}
|
|
|
|
}
|
|
|
|
|
|
|
|
wm := map[WarnableCode]UnhealthyState{}
|
|
|
|
|
|
|
|
for w, ws := range t.warnableVal {
|
|
|
|
wm[w.Code] = *w.unhealthyState(ws)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &State{
|
|
|
|
Warnings: wm,
|
|
|
|
}
|
|
|
|
}
|