| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package ipnlocal
- import (
- "context"
- "errors"
- "testing"
- "time"
- "tailscale.com/tailcfg"
- "tailscale.com/tstest"
- "tailscale.com/types/netmap"
- "tailscale.com/types/ptr"
- "tailscale.com/util/eventbus"
- )
- func TestNodeBackendReadiness(t *testing.T) {
- nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New())
- // The node backend is not ready until [nodeBackend.ready] is called,
- // and [nodeBackend.Wait] should fail with [context.DeadlineExceeded].
- ctx, cancelCtx := context.WithTimeout(context.Background(), 100*time.Millisecond)
- defer cancelCtx()
- if err := nb.Wait(ctx); err != ctx.Err() {
- t.Fatalf("Wait: got %v; want %v", err, ctx.Err())
- }
- // Start a goroutine to wait for the node backend to become ready.
- waitDone := make(chan struct{})
- go func() {
- if err := nb.Wait(context.Background()); err != nil {
- t.Errorf("Wait: got %v; want nil", err)
- }
- close(waitDone)
- }()
- // Call [nodeBackend.ready] to indicate that the node backend is now ready.
- go nb.ready()
- // Once the backend is called, [nodeBackend.Wait] should return immediately without error.
- if err := nb.Wait(context.Background()); err != nil {
- t.Fatalf("Wait: got %v; want nil", err)
- }
- // And any pending waiters should also be unblocked.
- <-waitDone
- }
- func TestNodeBackendShutdown(t *testing.T) {
- nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New())
- shutdownCause := errors.New("test shutdown")
- // Start a goroutine to wait for the node backend to become ready.
- // This test expects it to block until the node backend shuts down
- // and then return the specified shutdown cause.
- waitDone := make(chan struct{})
- go func() {
- if err := nb.Wait(context.Background()); err != shutdownCause {
- t.Errorf("Wait: got %v; want %v", err, shutdownCause)
- }
- close(waitDone)
- }()
- // Call [nodeBackend.shutdown] to indicate that the node backend is shutting down.
- nb.shutdown(shutdownCause)
- // Calling it again is fine, but should not change the shutdown cause.
- nb.shutdown(errors.New("test shutdown again"))
- // After shutdown, [nodeBackend.Wait] should return with the specified shutdown cause.
- if err := nb.Wait(context.Background()); err != shutdownCause {
- t.Fatalf("Wait: got %v; want %v", err, shutdownCause)
- }
- // The context associated with the node backend should also be cancelled
- // and its cancellation cause should match the shutdown cause.
- if err := nb.Context().Err(); !errors.Is(err, context.Canceled) {
- t.Fatalf("Context.Err: got %v; want %v", err, context.Canceled)
- }
- if cause := context.Cause(nb.Context()); cause != shutdownCause {
- t.Fatalf("Cause: got %v; want %v", cause, shutdownCause)
- }
- // And any pending waiters should also be unblocked.
- <-waitDone
- }
- func TestNodeBackendReadyAfterShutdown(t *testing.T) {
- nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New())
- shutdownCause := errors.New("test shutdown")
- nb.shutdown(shutdownCause)
- nb.ready() // Calling ready after shutdown is a no-op, but should not panic, etc.
- if err := nb.Wait(context.Background()); err != shutdownCause {
- t.Fatalf("Wait: got %v; want %v", err, shutdownCause)
- }
- }
- func TestNodeBackendParentContextCancellation(t *testing.T) {
- ctx, cancelCtx := context.WithCancel(context.Background())
- nb := newNodeBackend(ctx, tstest.WhileTestRunningLogger(t), eventbus.New())
- cancelCtx()
- // Cancelling the parent context should cause [nodeBackend.Wait]
- // to return with [context.Canceled].
- if err := nb.Wait(context.Background()); !errors.Is(err, context.Canceled) {
- t.Fatalf("Wait: got %v; want %v", err, context.Canceled)
- }
- // And the node backend's context should also be cancelled.
- if err := nb.Context().Err(); !errors.Is(err, context.Canceled) {
- t.Fatalf("Context.Err: got %v; want %v", err, context.Canceled)
- }
- }
- func TestNodeBackendConcurrentReadyAndShutdown(t *testing.T) {
- nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New())
- // Calling [nodeBackend.ready] and [nodeBackend.shutdown] concurrently
- // should not cause issues, and [nodeBackend.Wait] should unblock,
- // but the result of [nodeBackend.Wait] is intentionally undefined.
- go nb.ready()
- go nb.shutdown(errors.New("test shutdown"))
- nb.Wait(context.Background())
- }
- func TestNodeBackendReachability(t *testing.T) {
- for _, tc := range []struct {
- name string
- // Cap sets [tailcfg.NodeAttrClientSideReachability] on the self
- // node.
- //
- // When disabled, the client relies on the control plane sending
- // an accurate peer.Online flag. When enabled, the client
- // ignores peer.Online and determines whether it can reach the
- // peer node.
- cap bool
- peer tailcfg.Node
- want bool
- }{
- {
- name: "disabled/offline",
- cap: false,
- peer: tailcfg.Node{
- Online: ptr.To(false),
- },
- want: false,
- },
- {
- name: "disabled/online",
- cap: false,
- peer: tailcfg.Node{
- Online: ptr.To(true),
- },
- want: true,
- },
- {
- name: "enabled/offline",
- cap: true,
- peer: tailcfg.Node{
- Online: ptr.To(false),
- },
- want: true,
- },
- {
- name: "enabled/online",
- cap: true,
- peer: tailcfg.Node{
- Online: ptr.To(true),
- },
- want: true,
- },
- } {
- t.Run(tc.name, func(t *testing.T) {
- nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New())
- nb.netMap = &netmap.NetworkMap{}
- if tc.cap {
- nb.netMap.AllCaps.Make()
- nb.netMap.AllCaps.Add(tailcfg.NodeAttrClientSideReachability)
- }
- got := nb.PeerIsReachable(t.Context(), tc.peer.View())
- if got != tc.want {
- t.Errorf("got %v, want %v", got, tc.want)
- }
- })
- }
- }
|