mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-20 01:47:33 +00:00
controlclient,health,ipnlocal,tailcfg: add DisplayMessage support
Updates tailscale/corp#27759 Signed-off-by: James Sanderson <jsanderson@tailscale.com>
This commit is contained in:

committed by
James 'zofrex' Sanderson

parent
5b670eb3a5
commit
11e83f9da5
@@ -90,6 +90,7 @@ type mapSession struct {
|
||||
lastDomain string
|
||||
lastDomainAuditLogID string
|
||||
lastHealth []string
|
||||
lastDisplayMessages map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage
|
||||
lastPopBrowserURL string
|
||||
lastTKAInfo *tailcfg.TKAInfo
|
||||
lastNetmapSummary string // from NetworkMap.VeryConcise
|
||||
@@ -412,6 +413,21 @@ func (ms *mapSession) updateStateFromResponse(resp *tailcfg.MapResponse) {
|
||||
if resp.Health != nil {
|
||||
ms.lastHealth = resp.Health
|
||||
}
|
||||
if resp.DisplayMessages != nil {
|
||||
if v, ok := resp.DisplayMessages["*"]; ok && v == nil {
|
||||
ms.lastDisplayMessages = nil
|
||||
}
|
||||
for k, v := range resp.DisplayMessages {
|
||||
if k == "*" {
|
||||
continue
|
||||
}
|
||||
if v != nil {
|
||||
mak.Set(&ms.lastDisplayMessages, k, *v)
|
||||
} else {
|
||||
delete(ms.lastDisplayMessages, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
if resp.TKAInfo != nil {
|
||||
ms.lastTKAInfo = resp.TKAInfo
|
||||
}
|
||||
@@ -831,14 +847,19 @@ func (ms *mapSession) sortedPeers() []tailcfg.NodeView {
|
||||
func (ms *mapSession) netmap() *netmap.NetworkMap {
|
||||
peerViews := ms.sortedPeers()
|
||||
|
||||
// Convert all ms.lastHealth to the new [netmap.NetworkMap.DisplayMessages].
|
||||
var msgs map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage
|
||||
for _, h := range ms.lastHealth {
|
||||
mak.Set(&msgs, tailcfg.DisplayMessageID("control-health-"+strhash(h)), tailcfg.DisplayMessage{
|
||||
Title: "Coordination server reports an issue",
|
||||
Severity: tailcfg.SeverityMedium,
|
||||
Text: "The coordination server is reporting a health issue: " + h,
|
||||
})
|
||||
if len(ms.lastDisplayMessages) != 0 {
|
||||
msgs = ms.lastDisplayMessages
|
||||
} else if len(ms.lastHealth) > 0 {
|
||||
// Convert all ms.lastHealth to the new [netmap.NetworkMap.DisplayMessages]
|
||||
for _, h := range ms.lastHealth {
|
||||
id := "control-health-" + strhash(h) // Unique ID in case there is more than one health message
|
||||
mak.Set(&msgs, tailcfg.DisplayMessageID(id), tailcfg.DisplayMessage{
|
||||
Title: "Coordination server reports an issue",
|
||||
Severity: tailcfg.SeverityMedium,
|
||||
Text: "The coordination server is reporting a health issue: " + h,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
nm := &netmap.NetworkMap{
|
||||
|
@@ -16,6 +16,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"go4.org/mem"
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/health"
|
||||
@@ -1139,8 +1140,190 @@ func BenchmarkMapSessionDelta(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestNetmapDisplayMessage checks that the various diff operations
|
||||
// (add/update/delete/clear) for [tailcfg.DisplayMessage] in a
|
||||
// [tailcfg.MapResponse] work as expected.
|
||||
func TestNetmapDisplayMessage(t *testing.T) {
|
||||
type test struct {
|
||||
name string
|
||||
initialState *tailcfg.MapResponse
|
||||
mapResponse tailcfg.MapResponse
|
||||
wantMessages map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage
|
||||
}
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "basic-set",
|
||||
mapResponse: tailcfg.MapResponse{
|
||||
DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
|
||||
"test-message": {
|
||||
Title: "Testing",
|
||||
Text: "This is a test message",
|
||||
Severity: tailcfg.SeverityHigh,
|
||||
ImpactsConnectivity: true,
|
||||
PrimaryAction: &tailcfg.DisplayMessageAction{
|
||||
URL: "https://www.example.com",
|
||||
Label: "Learn more",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{
|
||||
"test-message": {
|
||||
Title: "Testing",
|
||||
Text: "This is a test message",
|
||||
Severity: tailcfg.SeverityHigh,
|
||||
ImpactsConnectivity: true,
|
||||
PrimaryAction: &tailcfg.DisplayMessageAction{
|
||||
URL: "https://www.example.com",
|
||||
Label: "Learn more",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "delete-one",
|
||||
initialState: &tailcfg.MapResponse{
|
||||
DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
|
||||
"message-a": {
|
||||
Title: "Message A",
|
||||
},
|
||||
"message-b": {
|
||||
Title: "Message B",
|
||||
},
|
||||
},
|
||||
},
|
||||
mapResponse: tailcfg.MapResponse{
|
||||
DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
|
||||
"message-a": nil,
|
||||
},
|
||||
},
|
||||
wantMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{
|
||||
"message-b": {
|
||||
Title: "Message B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update-one",
|
||||
initialState: &tailcfg.MapResponse{
|
||||
DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
|
||||
"message-a": {
|
||||
Title: "Message A",
|
||||
},
|
||||
"message-b": {
|
||||
Title: "Message B",
|
||||
},
|
||||
},
|
||||
},
|
||||
mapResponse: tailcfg.MapResponse{
|
||||
DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
|
||||
"message-a": {
|
||||
Title: "Message A updated",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{
|
||||
"message-a": {
|
||||
Title: "Message A updated",
|
||||
},
|
||||
"message-b": {
|
||||
Title: "Message B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add-one",
|
||||
initialState: &tailcfg.MapResponse{
|
||||
DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
|
||||
"message-a": {
|
||||
Title: "Message A",
|
||||
},
|
||||
},
|
||||
},
|
||||
mapResponse: tailcfg.MapResponse{
|
||||
DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
|
||||
"message-b": {
|
||||
Title: "Message B",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{
|
||||
"message-a": {
|
||||
Title: "Message A",
|
||||
},
|
||||
"message-b": {
|
||||
Title: "Message B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "delete-all",
|
||||
initialState: &tailcfg.MapResponse{
|
||||
DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
|
||||
"message-a": {
|
||||
Title: "Message A",
|
||||
},
|
||||
"message-b": {
|
||||
Title: "Message B",
|
||||
},
|
||||
},
|
||||
},
|
||||
mapResponse: tailcfg.MapResponse{
|
||||
DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
|
||||
"*": nil,
|
||||
},
|
||||
},
|
||||
wantMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{},
|
||||
},
|
||||
{
|
||||
name: "delete-all-and-add",
|
||||
initialState: &tailcfg.MapResponse{
|
||||
DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
|
||||
"message-a": {
|
||||
Title: "Message A",
|
||||
},
|
||||
"message-b": {
|
||||
Title: "Message B",
|
||||
},
|
||||
},
|
||||
},
|
||||
mapResponse: tailcfg.MapResponse{
|
||||
DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
|
||||
"*": nil,
|
||||
"message-c": {
|
||||
Title: "Message C",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{
|
||||
"message-c": {
|
||||
Title: "Message C",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ms := newTestMapSession(t, nil)
|
||||
|
||||
if test.initialState != nil {
|
||||
ms.netmapForResponse(test.initialState)
|
||||
}
|
||||
|
||||
nm := ms.netmapForResponse(&test.mapResponse)
|
||||
|
||||
if diff := cmp.Diff(test.wantMessages, nm.DisplayMessages, cmpopts.EquateEmpty()); diff != "" {
|
||||
t.Errorf("unexpected warnings (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNetmapHealthIntegration checks that we get the expected health warnings
|
||||
// from processing a map response and passing the NetworkMap to a health tracker
|
||||
// from processing a [tailcfg.MapResponse] containing health messages and passing the
|
||||
// [netmap.NetworkMap] to a [health.Tracker].
|
||||
func TestNetmapHealthIntegration(t *testing.T) {
|
||||
ms := newTestMapSession(t, nil)
|
||||
ht := health.Tracker{}
|
||||
@@ -1182,3 +1365,56 @@ func TestNetmapHealthIntegration(t *testing.T) {
|
||||
t.Fatalf("CurrentStatus().Warnings[\"control-health*\"] different than expected (-want +got)\n%s", d)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNetmapDisplayMessageIntegration checks that we get the expected health
|
||||
// warnings from processing a [tailcfg.MapResponse] that contains DisplayMessages and
|
||||
// passing the [netmap.NetworkMap] to a [health.Tracker].
|
||||
func TestNetmapDisplayMessageIntegration(t *testing.T) {
|
||||
ms := newTestMapSession(t, nil)
|
||||
ht := health.Tracker{}
|
||||
|
||||
ht.SetIPNState("NeedsLogin", true)
|
||||
ht.GotStreamedMapResponse()
|
||||
baseWarnings := ht.CurrentState().Warnings
|
||||
|
||||
nm := ms.netmapForResponse(&tailcfg.MapResponse{
|
||||
DisplayMessages: map[tailcfg.DisplayMessageID]*tailcfg.DisplayMessage{
|
||||
"test-message": {
|
||||
Title: "Testing",
|
||||
Text: "This is a test message",
|
||||
Severity: tailcfg.SeverityHigh,
|
||||
ImpactsConnectivity: true,
|
||||
PrimaryAction: &tailcfg.DisplayMessageAction{
|
||||
URL: "https://www.example.com",
|
||||
Label: "Learn more",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
ht.SetControlHealth(nm.DisplayMessages)
|
||||
|
||||
state := ht.CurrentState()
|
||||
|
||||
// Ignore warnings that aren't from the netmap
|
||||
for k := range baseWarnings {
|
||||
delete(state.Warnings, k)
|
||||
}
|
||||
|
||||
want := map[health.WarnableCode]health.UnhealthyState{
|
||||
"test-message": {
|
||||
WarnableCode: "test-message",
|
||||
Title: "Testing",
|
||||
Text: "This is a test message",
|
||||
Severity: health.SeverityHigh,
|
||||
ImpactsConnectivity: true,
|
||||
PrimaryAction: &health.UnhealthyStateAction{
|
||||
URL: "https://www.example.com",
|
||||
Label: "Learn more",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, state.Warnings); diff != "" {
|
||||
t.Errorf("unexpected message contents (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user