health: prefix Warnables received from the control plane

Updates tailscale/corp#27759

Signed-off-by: James Sanderson <jsanderson@tailscale.com>
This commit is contained in:
James Sanderson 2025-06-06 15:53:30 +01:00 committed by James 'zofrex' Sanderson
parent 7b06532ea1
commit 5716d0977d
5 changed files with 34 additions and 26 deletions

View File

@ -853,7 +853,7 @@ func (ms *mapSession) netmap() *netmap.NetworkMap {
} else if len(ms.lastHealth) > 0 { } else if len(ms.lastHealth) > 0 {
// Convert all ms.lastHealth to the new [netmap.NetworkMap.DisplayMessages] // Convert all ms.lastHealth to the new [netmap.NetworkMap.DisplayMessages]
for _, h := range ms.lastHealth { for _, h := range ms.lastHealth {
id := "control-health-" + strhash(h) // Unique ID in case there is more than one health message id := "health-" + strhash(h) // Unique ID in case there is more than one health message
mak.Set(&msgs, tailcfg.DisplayMessageID(id), tailcfg.DisplayMessage{ mak.Set(&msgs, tailcfg.DisplayMessageID(id), tailcfg.DisplayMessage{
Title: "Coordination server reports an issue", Title: "Coordination server reports an issue",
Severity: tailcfg.SeverityMedium, Severity: tailcfg.SeverityMedium,

View File

@ -1340,14 +1340,14 @@ func TestNetmapHealthIntegration(t *testing.T) {
ht.SetControlHealth(nm.DisplayMessages) ht.SetControlHealth(nm.DisplayMessages)
want := map[health.WarnableCode]health.UnhealthyState{ want := map[health.WarnableCode]health.UnhealthyState{
"control-health-c0719e9a8d5d838d861dc6f675c899d2b309a3a65bb9fe6b11e5afcbf9a2c0b1": { "control-health.health-c0719e9a8d5d838d861dc6f675c899d2b309a3a65bb9fe6b11e5afcbf9a2c0b1": {
WarnableCode: "control-health-c0719e9a8d5d838d861dc6f675c899d2b309a3a65bb9fe6b11e5afcbf9a2c0b1", WarnableCode: "control-health.health-c0719e9a8d5d838d861dc6f675c899d2b309a3a65bb9fe6b11e5afcbf9a2c0b1",
Title: "Coordination server reports an issue", Title: "Coordination server reports an issue",
Severity: health.SeverityMedium, Severity: health.SeverityMedium,
Text: "The coordination server is reporting a health issue: Test message", Text: "The coordination server is reporting a health issue: Test message",
}, },
"control-health-1dc7017a73a3c55c0d6a8423e3813c7ab6562d9d3064c2ec6ac7822f61b1db9c": { "control-health.health-1dc7017a73a3c55c0d6a8423e3813c7ab6562d9d3064c2ec6ac7822f61b1db9c": {
WarnableCode: "control-health-1dc7017a73a3c55c0d6a8423e3813c7ab6562d9d3064c2ec6ac7822f61b1db9c", WarnableCode: "control-health.health-1dc7017a73a3c55c0d6a8423e3813c7ab6562d9d3064c2ec6ac7822f61b1db9c",
Title: "Coordination server reports an issue", Title: "Coordination server reports an issue",
Severity: health.SeverityMedium, Severity: health.SeverityMedium,
Text: "The coordination server is reporting a health issue: Another message", Text: "The coordination server is reporting a health issue: Another message",
@ -1401,8 +1401,8 @@ func TestNetmapDisplayMessageIntegration(t *testing.T) {
} }
want := map[health.WarnableCode]health.UnhealthyState{ want := map[health.WarnableCode]health.UnhealthyState{
"test-message": { "control-health.test-message": {
WarnableCode: "test-message", WarnableCode: "control-health.test-message",
Title: "Testing", Title: "Testing",
Text: "This is a test message", Text: "This is a test message",
Severity: health.SeverityHigh, Severity: health.SeverityHigh,

View File

@ -468,14 +468,14 @@ func TestControlHealth(t *testing.T) {
baseStrs := ht.Strings() baseStrs := ht.Strings()
msgs := map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{ msgs := map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{
"control-health-test": { "test": {
Title: "Control health message", Title: "Control health message",
Text: "Extra help.", Text: "Extra help.",
}, },
"control-health-title": { "title": {
Title: "Control health title only", Title: "Control health title only",
}, },
"control-health-with-action": { "with-action": {
Title: "Control health message", Title: "Control health message",
Text: "Extra help.", Text: "Extra help.",
PrimaryAction: &tailcfg.DisplayMessageAction{ PrimaryAction: &tailcfg.DisplayMessageAction{
@ -488,19 +488,19 @@ func TestControlHealth(t *testing.T) {
t.Run("Warnings", func(t *testing.T) { t.Run("Warnings", func(t *testing.T) {
wantWarns := map[WarnableCode]UnhealthyState{ wantWarns := map[WarnableCode]UnhealthyState{
"control-health-test": { "control-health.test": {
WarnableCode: "control-health-test", WarnableCode: "control-health.test",
Severity: SeverityMedium, Severity: SeverityMedium,
Title: "Control health message", Title: "Control health message",
Text: "Extra help.", Text: "Extra help.",
}, },
"control-health-title": { "control-health.title": {
WarnableCode: "control-health-title", WarnableCode: "control-health.title",
Severity: SeverityMedium, Severity: SeverityMedium,
Title: "Control health title only", Title: "Control health title only",
}, },
"control-health-with-action": { "control-health.with-action": {
WarnableCode: "control-health-with-action", WarnableCode: "control-health.with-action",
Severity: SeverityMedium, Severity: SeverityMedium,
Title: "Control health message", Title: "Control health message",
Text: "Extra help.", Text: "Extra help.",

View File

@ -112,7 +112,7 @@ func (t *Tracker) CurrentState() *State {
for id, msg := range t.lastNotifiedControlMessages { for id, msg := range t.lastNotifiedControlMessages {
state := UnhealthyState{ state := UnhealthyState{
WarnableCode: WarnableCode(id), WarnableCode: WarnableCode("control-health." + id),
Severity: severityFromTailcfg(msg.Severity), Severity: severityFromTailcfg(msg.Severity),
Title: msg.Title, Title: msg.Title,
Text: msg.Text, Text: msg.Text,

View File

@ -8,6 +8,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"maps"
"math" "math"
"net" "net"
"net/http" "net/http"
@ -5422,10 +5423,11 @@ func TestDisplayMessages(t *testing.T) {
}) })
state := ht.CurrentState() state := ht.CurrentState()
_, ok := state.Warnings["test-message"] wantID := health.WarnableCode("control-health.test-message")
_, ok := state.Warnings[wantID]
if !ok { if !ok {
t.Error("no warning found with id 'test-message'") t.Errorf("no warning found with id %q", wantID)
} }
} }
@ -5455,14 +5457,15 @@ func TestDisplayMessagesURLFilter(t *testing.T) {
}) })
state := ht.CurrentState() state := ht.CurrentState()
got, ok := state.Warnings["test-message"] wantID := health.WarnableCode("control-health.test-message")
got, ok := state.Warnings[wantID]
if !ok { if !ok {
t.Fatal("no warning found with id 'test-message'") t.Fatalf("no warning found with id %q", wantID)
} }
want := health.UnhealthyState{ want := health.UnhealthyState{
WarnableCode: "test-message", WarnableCode: wantID,
Title: "Testing", Title: "Testing",
Severity: health.SeverityHigh, Severity: health.SeverityHigh,
} }
@ -5494,12 +5497,14 @@ func TestDisplayMessageIPNBus(t *testing.T) {
}, },
} }
wantID := health.WarnableCode("control-health.test-message")
for _, tt := range []test{ for _, tt := range []test{
{ {
name: "older-client-no-actions", name: "older-client-no-actions",
mask: 0, mask: 0,
wantWarning: health.UnhealthyState{ wantWarning: health.UnhealthyState{
WarnableCode: "test-message", WarnableCode: wantID,
Severity: health.SeverityMedium, Severity: health.SeverityMedium,
Title: "Message title", Title: "Message title",
Text: "Message text. Learn more: https://example.com", // PrimaryAction appended to text Text: "Message text. Learn more: https://example.com", // PrimaryAction appended to text
@ -5510,7 +5515,7 @@ func TestDisplayMessageIPNBus(t *testing.T) {
name: "new-client-with-actions", name: "new-client-with-actions",
mask: ipn.NotifyHealthActions, mask: ipn.NotifyHealthActions,
wantWarning: health.UnhealthyState{ wantWarning: health.UnhealthyState{
WarnableCode: "test-message", WarnableCode: wantID,
Severity: health.SeverityMedium, Severity: health.SeverityMedium,
Title: "Message title", Title: "Message title",
Text: "Message text.", Text: "Message text.",
@ -5530,17 +5535,20 @@ func TestDisplayMessageIPNBus(t *testing.T) {
ipnWatcher := newNotificationWatcher(t, lb, nil) ipnWatcher := newNotificationWatcher(t, lb, nil)
ipnWatcher.watch(tt.mask, []wantedNotification{{ ipnWatcher.watch(tt.mask, []wantedNotification{{
name: "test", name: fmt.Sprintf("warning with ID %q", wantID),
cond: func(_ testing.TB, _ ipnauth.Actor, n *ipn.Notify) bool { cond: func(_ testing.TB, _ ipnauth.Actor, n *ipn.Notify) bool {
if n.Health == nil { if n.Health == nil {
return false return false
} }
got, ok := n.Health.Warnings["test-message"] got, ok := n.Health.Warnings[wantID]
if ok { if ok {
if diff := cmp.Diff(tt.wantWarning, got); diff != "" { if diff := cmp.Diff(tt.wantWarning, got); diff != "" {
t.Errorf("unexpected warning details (-want/+got):\n%s", diff) t.Errorf("unexpected warning details (-want/+got):\n%s", diff)
return true // we failed the test so tell the watcher we've seen what we need to to stop it waiting return true // we failed the test so tell the watcher we've seen what we need to to stop it waiting
} }
} else {
got := slices.Collect(maps.Keys(n.Health.Warnings))
t.Logf("saw warnings: %v", got)
} }
return ok return ok
}, },