mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-11 21:27:31 +00:00
control/controlclient: convert PeersChanged nodes to patches internally
So even if the server doesn't support sending patches (neither the Tailscale control server nor Headscale yet do), this makes the client convert a changed node to its diff so the diffs can be processed individually in a follow-up change. This lets us make progress on #1909 without adding a dependency on finishing the server-side part, and also means other control servers will get the same upcoming optimizations. And add some clientmetrics while here. Updates #1909 Change-Id: I9533bcb8bba5227e17389f0b10dff71f33ee54ec Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:

committed by
Brad Fitzpatrick

parent
67e48d9285
commit
a79b1d23b8
@@ -4,6 +4,7 @@
|
||||
package controlclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"go4.org/mem"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstest"
|
||||
@@ -642,6 +644,132 @@ func TestDeltaDERPMap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeerChangeDiff(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a, b *tailcfg.Node
|
||||
want *tailcfg.PeerChange // nil means want ok=false, unless wantEqual is set
|
||||
wantEqual bool // means test wants (nil, true)
|
||||
}{
|
||||
{
|
||||
name: "eq",
|
||||
a: &tailcfg.Node{ID: 1},
|
||||
b: &tailcfg.Node{ID: 1},
|
||||
wantEqual: true,
|
||||
},
|
||||
{
|
||||
name: "patch-derp",
|
||||
a: &tailcfg.Node{ID: 1, DERP: "127.3.3.40:1"},
|
||||
b: &tailcfg.Node{ID: 1, DERP: "127.3.3.40:2"},
|
||||
want: &tailcfg.PeerChange{NodeID: 1, DERPRegion: 2},
|
||||
},
|
||||
{
|
||||
name: "patch-endpoints",
|
||||
a: &tailcfg.Node{ID: 1, Endpoints: []string{"10.0.0.1:1"}},
|
||||
b: &tailcfg.Node{ID: 1, Endpoints: []string{"10.0.0.2:2"}},
|
||||
want: &tailcfg.PeerChange{NodeID: 1, Endpoints: []string{"10.0.0.2:2"}},
|
||||
},
|
||||
{
|
||||
name: "patch-cap",
|
||||
a: &tailcfg.Node{ID: 1, Cap: 1},
|
||||
b: &tailcfg.Node{ID: 1, Cap: 2},
|
||||
want: &tailcfg.PeerChange{NodeID: 1, Cap: 2},
|
||||
},
|
||||
{
|
||||
name: "patch-lastseen",
|
||||
a: &tailcfg.Node{ID: 1, LastSeen: ptr.To(time.Unix(1, 0))},
|
||||
b: &tailcfg.Node{ID: 1, LastSeen: ptr.To(time.Unix(2, 0))},
|
||||
want: &tailcfg.PeerChange{NodeID: 1, LastSeen: ptr.To(time.Unix(2, 0))},
|
||||
},
|
||||
{
|
||||
name: "patch-capabilities-to-nonempty",
|
||||
a: &tailcfg.Node{ID: 1, Capabilities: []string{"foo"}},
|
||||
b: &tailcfg.Node{ID: 1, Capabilities: []string{"bar"}},
|
||||
want: &tailcfg.PeerChange{NodeID: 1, Capabilities: ptr.To([]string{"bar"})},
|
||||
},
|
||||
{
|
||||
name: "patch-capabilities-to-empty",
|
||||
a: &tailcfg.Node{ID: 1, Capabilities: []string{"foo"}},
|
||||
b: &tailcfg.Node{ID: 1},
|
||||
want: &tailcfg.PeerChange{NodeID: 1, Capabilities: ptr.To([]string(nil))},
|
||||
},
|
||||
{
|
||||
name: "patch-online-to-true",
|
||||
a: &tailcfg.Node{ID: 1, Online: ptr.To(false)},
|
||||
b: &tailcfg.Node{ID: 1, Online: ptr.To(true)},
|
||||
want: &tailcfg.PeerChange{NodeID: 1, Online: ptr.To(true)},
|
||||
},
|
||||
{
|
||||
name: "patch-online-to-false",
|
||||
a: &tailcfg.Node{ID: 1, Online: ptr.To(true)},
|
||||
b: &tailcfg.Node{ID: 1, Online: ptr.To(false)},
|
||||
want: &tailcfg.PeerChange{NodeID: 1, Online: ptr.To(false)},
|
||||
},
|
||||
{
|
||||
name: "mix-patchable-and-not",
|
||||
a: &tailcfg.Node{ID: 1, Cap: 1},
|
||||
b: &tailcfg.Node{ID: 1, Cap: 2, StableID: "foo"},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "miss-change-stableid",
|
||||
a: &tailcfg.Node{ID: 1},
|
||||
b: &tailcfg.Node{ID: 1, StableID: "diff"},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "miss-change-id",
|
||||
a: &tailcfg.Node{ID: 1},
|
||||
b: &tailcfg.Node{ID: 2},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "miss-change-name",
|
||||
a: &tailcfg.Node{ID: 1, Name: "foo"},
|
||||
b: &tailcfg.Node{ID: 1, Name: "bar"},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "miss-change-user",
|
||||
a: &tailcfg.Node{ID: 1, User: 1},
|
||||
b: &tailcfg.Node{ID: 1, User: 2},
|
||||
want: nil,
|
||||
}}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pc, ok := peerChangeDiff(tt.a.View(), tt.b)
|
||||
if tt.wantEqual {
|
||||
if !ok || pc != nil {
|
||||
t.Errorf("got (%p, %v); want (nil, true); pc=%v", pc, ok, must.Get(json.Marshal(pc)))
|
||||
}
|
||||
return
|
||||
}
|
||||
if (pc != nil) != ok {
|
||||
t.Fatalf("inconsistent ok=%v, pc=%p", ok, pc)
|
||||
}
|
||||
gotj := must.Get(json.Marshal(pc))
|
||||
wantj := must.Get(json.Marshal(tt.want))
|
||||
if !bytes.Equal(gotj, wantj) {
|
||||
t.Errorf("mismatch\n got: %s\nwant: %s\n", gotj, wantj)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeerChangeDiffAllocs(t *testing.T) {
|
||||
a := &tailcfg.Node{ID: 1}
|
||||
b := &tailcfg.Node{ID: 1}
|
||||
n := testing.AllocsPerRun(10000, func() {
|
||||
diff, ok := peerChangeDiff(a.View(), b)
|
||||
if !ok || diff != nil {
|
||||
t.Fatalf("unexpected result: (%s, %v)", must.Get(json.Marshal(diff)), ok)
|
||||
}
|
||||
})
|
||||
if n != 0 {
|
||||
t.Errorf("allocs = %v; want 0", int(n))
|
||||
}
|
||||
}
|
||||
|
||||
type countingNetmapUpdater struct {
|
||||
full atomic.Int64
|
||||
}
|
||||
@@ -650,6 +778,80 @@ func (nu *countingNetmapUpdater) UpdateFullNetmap(nm *netmap.NetworkMap) {
|
||||
nu.full.Add(1)
|
||||
}
|
||||
|
||||
// tests (*mapSession).patchifyPeersChanged; smaller tests are in TestPeerChangeDiff
|
||||
func TestPatchifyPeersChanged(t *testing.T) {
|
||||
hi := (&tailcfg.Hostinfo{}).View()
|
||||
tests := []struct {
|
||||
name string
|
||||
mr0 *tailcfg.MapResponse // initial
|
||||
mr1 *tailcfg.MapResponse // incremental
|
||||
want *tailcfg.MapResponse // what the incremental one should've been mutated to
|
||||
}{
|
||||
{
|
||||
name: "change_one_endpoint",
|
||||
mr0: &tailcfg.MapResponse{
|
||||
Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
|
||||
Peers: []*tailcfg.Node{
|
||||
{ID: 1, Hostinfo: hi},
|
||||
},
|
||||
},
|
||||
mr1: &tailcfg.MapResponse{
|
||||
PeersChanged: []*tailcfg.Node{
|
||||
{ID: 1, Endpoints: []string{"10.0.0.1:1111"}, Hostinfo: hi},
|
||||
},
|
||||
},
|
||||
want: &tailcfg.MapResponse{
|
||||
PeersChanged: nil,
|
||||
PeersChangedPatch: []*tailcfg.PeerChange{
|
||||
{NodeID: 1, Endpoints: []string{"10.0.0.1:1111"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change_some",
|
||||
mr0: &tailcfg.MapResponse{
|
||||
Node: &tailcfg.Node{Name: "foo.bar.ts.net."},
|
||||
Peers: []*tailcfg.Node{
|
||||
{ID: 1, DERP: "127.3.3.40:1", Hostinfo: hi},
|
||||
{ID: 2, DERP: "127.3.3.40:2", Hostinfo: hi},
|
||||
{ID: 3, DERP: "127.3.3.40:3", Hostinfo: hi},
|
||||
},
|
||||
},
|
||||
mr1: &tailcfg.MapResponse{
|
||||
PeersChanged: []*tailcfg.Node{
|
||||
{ID: 1, DERP: "127.3.3.40:11", Hostinfo: hi},
|
||||
{ID: 2, StableID: "other-change", Hostinfo: hi},
|
||||
{ID: 3, DERP: "127.3.3.40:33", Hostinfo: hi},
|
||||
{ID: 4, DERP: "127.3.3.40:4", Hostinfo: hi},
|
||||
},
|
||||
},
|
||||
want: &tailcfg.MapResponse{
|
||||
PeersChanged: []*tailcfg.Node{
|
||||
{ID: 2, StableID: "other-change", Hostinfo: hi},
|
||||
{ID: 4, DERP: "127.3.3.40:4", Hostinfo: hi},
|
||||
},
|
||||
PeersChangedPatch: []*tailcfg.PeerChange{
|
||||
{NodeID: 1, DERPRegion: 11},
|
||||
{NodeID: 3, DERPRegion: 33},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
nu := &countingNetmapUpdater{}
|
||||
ms := newTestMapSession(t, nu)
|
||||
ms.updateStateFromResponse(tt.mr0)
|
||||
mr1 := new(tailcfg.MapResponse)
|
||||
must.Do(json.Unmarshal(must.Get(json.Marshal(tt.mr1)), mr1))
|
||||
ms.patchifyPeersChanged(mr1)
|
||||
if diff := cmp.Diff(tt.want, mr1); diff != "" {
|
||||
t.Errorf("wrong result (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapSessionDelta(b *testing.B) {
|
||||
for _, size := range []int{10, 100, 1_000, 10_000} {
|
||||
b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
|
||||
@@ -700,5 +902,4 @@ func BenchmarkMapSessionDelta(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user