mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-24 09:36:54 +00:00
tailcfg, control/controlclient: start moving MapResponse.DefaultAutoUpdate to a nodeattr
And fix up the TestAutoUpdateDefaults integration tests as they weren't testing reality: the DefaultAutoUpdate is supposed to only be relevant on the first MapResponse in the stream, but the tests weren't testing that. They were instead injecting a 2nd+ MapResponse. This changes the test control server to add a hook to modify the first map response, and then makes the test control when the node goes up and down to make new map responses. Also, the test now runs on macOS where the auto-update feature being disabled would've previously t.Skipped the whole test. Updates #11502 Change-Id: If2319bd1f71e108b57d79fe500b2acedbc76e1a6 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
848978e664
commit
ac0b15356d
@@ -576,6 +576,7 @@ type TestNode struct {
|
||||
stateFile string
|
||||
upFlagGOOS string // if non-empty, sets TS_DEBUG_UP_FLAG_GOOS for cmd/tailscale CLI
|
||||
encryptState bool
|
||||
allowUpdates bool
|
||||
|
||||
mu sync.Mutex
|
||||
onLogLine []func([]byte)
|
||||
@@ -840,6 +841,9 @@ func (n *TestNode) StartDaemonAsIPNGOOS(ipnGOOS string) *Daemon {
|
||||
"TS_DISABLE_PORTMAPPER=1", // shouldn't be needed; test is all localhost
|
||||
"TS_DEBUG_LOG_RATE=all",
|
||||
)
|
||||
if n.allowUpdates {
|
||||
cmd.Env = append(cmd.Env, "TS_TEST_ALLOW_AUTO_UPDATE=1")
|
||||
}
|
||||
if n.env.loopbackPort != nil {
|
||||
cmd.Env = append(cmd.Env, "TS_DEBUG_NETSTACK_LOOPBACK_PORT="+strconv.Itoa(*n.env.loopbackPort))
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -1412,14 +1413,27 @@ func TestLogoutRemovesAllPeers(t *testing.T) {
|
||||
wantNode0PeerCount(expectedPeers) // all existing peers and the new node
|
||||
}
|
||||
|
||||
func TestAutoUpdateDefaults(t *testing.T) {
|
||||
if !feature.CanAutoUpdate() {
|
||||
t.Skip("auto-updates not supported on this platform")
|
||||
}
|
||||
func TestAutoUpdateDefaults(t *testing.T) { testAutoUpdateDefaults(t, false) }
|
||||
func TestAutoUpdateDefaults_cap(t *testing.T) { testAutoUpdateDefaults(t, true) }
|
||||
|
||||
// useCap is whether to use NodeAttrDefaultAutoUpdate (as opposed to the old
|
||||
// DeprecatedDefaultAutoUpdate top-level MapResponse field).
|
||||
func testAutoUpdateDefaults(t *testing.T, useCap bool) {
|
||||
t.Cleanup(feature.HookCanAutoUpdate.SetForTest(func() bool { return true }))
|
||||
|
||||
tstest.Shard(t)
|
||||
tstest.Parallel(t)
|
||||
env := NewTestEnv(t)
|
||||
|
||||
var (
|
||||
modifyMu sync.Mutex
|
||||
modifyFirstMapResponse = func(*tailcfg.MapResponse, *tailcfg.MapRequest) {}
|
||||
)
|
||||
env.Control.ModifyFirstMapResponse = func(mr *tailcfg.MapResponse, req *tailcfg.MapRequest) {
|
||||
modifyMu.Lock()
|
||||
defer modifyMu.Unlock()
|
||||
modifyFirstMapResponse(mr, req)
|
||||
}
|
||||
|
||||
checkDefault := func(n *TestNode, want bool) error {
|
||||
enabled, ok := n.diskPrefs().AutoUpdate.Apply.Get()
|
||||
if !ok {
|
||||
@@ -1431,17 +1445,23 @@ func TestAutoUpdateDefaults(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
sendAndCheckDefault := func(t *testing.T, n *TestNode, send, want bool) {
|
||||
t.Helper()
|
||||
if !env.Control.AddRawMapResponse(n.MustStatus().Self.PublicKey, &tailcfg.MapResponse{
|
||||
DefaultAutoUpdate: opt.NewBool(send),
|
||||
}) {
|
||||
t.Fatal("failed to send MapResponse to node")
|
||||
}
|
||||
if err := tstest.WaitFor(2*time.Second, func() error {
|
||||
return checkDefault(n, want)
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
setDefaultAutoUpdate := func(send bool) {
|
||||
modifyMu.Lock()
|
||||
defer modifyMu.Unlock()
|
||||
modifyFirstMapResponse = func(mr *tailcfg.MapResponse, req *tailcfg.MapRequest) {
|
||||
if mr.Node == nil {
|
||||
mr.Node = &tailcfg.Node{}
|
||||
}
|
||||
if useCap {
|
||||
if mr.Node.CapMap == nil {
|
||||
mr.Node.CapMap = make(tailcfg.NodeCapMap)
|
||||
}
|
||||
mr.Node.CapMap[tailcfg.NodeAttrDefaultAutoUpdate] = []tailcfg.RawMessage{
|
||||
tailcfg.RawMessage(fmt.Sprintf("%t", send)),
|
||||
}
|
||||
} else {
|
||||
mr.DeprecatedDefaultAutoUpdate = opt.NewBool(send)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1452,29 +1472,54 @@ func TestAutoUpdateDefaults(t *testing.T) {
|
||||
{
|
||||
desc: "tailnet-default-false",
|
||||
run: func(t *testing.T, n *TestNode) {
|
||||
// First received default "false".
|
||||
sendAndCheckDefault(t, n, false, false)
|
||||
// Should not be changed even if sent "true" later.
|
||||
sendAndCheckDefault(t, n, true, false)
|
||||
|
||||
// First the server sends "false", and client should remember that.
|
||||
setDefaultAutoUpdate(false)
|
||||
n.MustUp()
|
||||
n.AwaitRunning()
|
||||
checkDefault(n, false)
|
||||
|
||||
// Now we disconnect and change the server to send "true", which
|
||||
// the client should ignore, having previously remembered
|
||||
// "false".
|
||||
n.MustDown()
|
||||
setDefaultAutoUpdate(true) // control sends default "true"
|
||||
n.MustUp()
|
||||
n.AwaitRunning()
|
||||
checkDefault(n, false) // still false
|
||||
|
||||
// But can be changed explicitly by the user.
|
||||
if out, err := n.TailscaleForOutput("set", "--auto-update").CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to enable auto-update on node: %v\noutput: %s", err, out)
|
||||
}
|
||||
sendAndCheckDefault(t, n, false, true)
|
||||
checkDefault(n, true)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "tailnet-default-true",
|
||||
run: func(t *testing.T, n *TestNode) {
|
||||
// First received default "true".
|
||||
sendAndCheckDefault(t, n, true, true)
|
||||
// Should not be changed even if sent "false" later.
|
||||
sendAndCheckDefault(t, n, false, true)
|
||||
// Same as above but starting with default "true".
|
||||
|
||||
// First the server sends "true", and client should remember that.
|
||||
setDefaultAutoUpdate(true)
|
||||
n.MustUp()
|
||||
n.AwaitRunning()
|
||||
checkDefault(n, true)
|
||||
|
||||
// Now we disconnect and change the server to send "false", which
|
||||
// the client should ignore, having previously remembered
|
||||
// "true".
|
||||
n.MustDown()
|
||||
setDefaultAutoUpdate(false) // control sends default "false"
|
||||
n.MustUp()
|
||||
n.AwaitRunning()
|
||||
checkDefault(n, true) // still true
|
||||
|
||||
// But can be changed explicitly by the user.
|
||||
if out, err := n.TailscaleForOutput("set", "--auto-update=false").CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to disable auto-update on node: %v\noutput: %s", err, out)
|
||||
t.Fatalf("failed to enable auto-update on node: %v\noutput: %s", err, out)
|
||||
}
|
||||
sendAndCheckDefault(t, n, true, false)
|
||||
checkDefault(n, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1484,22 +1529,21 @@ func TestAutoUpdateDefaults(t *testing.T) {
|
||||
if out, err := n.TailscaleForOutput("set", "--auto-update=false").CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to disable auto-update on node: %v\noutput: %s", err, out)
|
||||
}
|
||||
// Defaults sent from control should be ignored.
|
||||
sendAndCheckDefault(t, n, true, false)
|
||||
sendAndCheckDefault(t, n, false, false)
|
||||
|
||||
setDefaultAutoUpdate(true)
|
||||
n.MustUp()
|
||||
n.AwaitRunning()
|
||||
checkDefault(n, false)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
n := NewTestNode(t, env)
|
||||
n.allowUpdates = true
|
||||
d := n.StartDaemon()
|
||||
defer d.MustCleanShutdown(t)
|
||||
|
||||
n.AwaitResponding()
|
||||
n.MustUp()
|
||||
n.AwaitRunning()
|
||||
|
||||
tt.run(t, n)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -79,6 +79,10 @@ type Server struct {
|
||||
ExplicitBaseURL string // e.g. "http://127.0.0.1:1234" with no trailing URL
|
||||
HTTPTestServer *httptest.Server // if non-nil, used to get BaseURL
|
||||
|
||||
// ModifyFirstMapResponse, if non-nil, is called exactly once per
|
||||
// MapResponse stream to modify the first MapResponse sent in response to it.
|
||||
ModifyFirstMapResponse func(*tailcfg.MapResponse, *tailcfg.MapRequest)
|
||||
|
||||
initMuxOnce sync.Once
|
||||
mux *http.ServeMux
|
||||
|
||||
@@ -993,6 +997,7 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey key.Machi
|
||||
// register an updatesCh to get updates.
|
||||
streaming := req.Stream && !req.ReadOnly
|
||||
compress := req.Compress != ""
|
||||
first := true
|
||||
|
||||
w.WriteHeader(200)
|
||||
for {
|
||||
@@ -1025,6 +1030,10 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey key.Machi
|
||||
if allExpired {
|
||||
res.Node.KeyExpiry = time.Now().Add(-1 * time.Minute)
|
||||
}
|
||||
if f := s.ModifyFirstMapResponse; first && f != nil {
|
||||
first = false
|
||||
f(res, req)
|
||||
}
|
||||
// TODO: add minner if/when needed
|
||||
resBytes, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user