mirror of
https://github.com/tailscale/tailscale.git
synced 2025-05-21 09:48:31 +00:00

Updates #15970 Updates #15812 Updates tailscale/corp#28449 Change-Id: I52cf25f98636b0beac16275f46e58d0816963895 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
198 lines
4.7 KiB
Go
198 lines
4.7 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package taildrop_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"testing"
|
|
"time"
|
|
|
|
"tailscale.com/client/local"
|
|
"tailscale.com/client/tailscale/apitype"
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/tstest"
|
|
"tailscale.com/tstest/integration"
|
|
"tailscale.com/tstest/integration/testcontrol"
|
|
)
|
|
|
|
// TODO(bradfitz): add test where control doesn't send tailcfg.CapabilityFileSharing
|
|
// and verify that we get the "file sharing not enabled by Tailscale admin" error.
|
|
|
|
// TODO(bradfitz): add test between different users with the peercap to permit that?
|
|
|
|
func TestTaildropIntegration(t *testing.T) {
|
|
t.Skip("known failing test; see https://github.com/tailscale/tailscale/issues/15970")
|
|
testTaildropIntegration(t, false)
|
|
}
|
|
|
|
func TestTaildropIntegration_Fresh(t *testing.T) {
|
|
testTaildropIntegration(t, true)
|
|
}
|
|
|
|
// freshProfiles is whether to start the test right away
|
|
// with a fresh profile. If false, tailscaled is started, stopped,
|
|
// and restarted again to simulate a real-world scenario where
|
|
// the first profile already existed.
|
|
//
|
|
// This exercises an ipnext hook ordering issue we hit earlier.
|
|
func testTaildropIntegration(t *testing.T, freshProfiles bool) {
|
|
tstest.Parallel(t)
|
|
controlOpt := integration.ConfigureControl(func(s *testcontrol.Server) {
|
|
s.AllNodesSameUser = true // required for Taildrop
|
|
})
|
|
env := integration.NewTestEnv(t, controlOpt)
|
|
|
|
// Create two nodes:
|
|
n1 := integration.NewTestNode(t, env)
|
|
d1 := n1.StartDaemon()
|
|
|
|
n2 := integration.NewTestNode(t, env)
|
|
d2 := n2.StartDaemon()
|
|
|
|
awaitUp := func() {
|
|
t.Helper()
|
|
n1.AwaitListening()
|
|
t.Logf("n1 is listening")
|
|
n2.AwaitListening()
|
|
t.Logf("n2 is listening")
|
|
n1.MustUp()
|
|
t.Logf("n1 is up")
|
|
n2.MustUp()
|
|
t.Logf("n2 is up")
|
|
n1.AwaitRunning()
|
|
t.Logf("n1 is running")
|
|
n2.AwaitRunning()
|
|
t.Logf("n2 is running")
|
|
}
|
|
awaitUp()
|
|
|
|
if !freshProfiles {
|
|
d1.MustCleanShutdown(t)
|
|
d2.MustCleanShutdown(t)
|
|
d1 = n1.StartDaemon()
|
|
d2 = n2.StartDaemon()
|
|
awaitUp()
|
|
}
|
|
|
|
var peerStableID tailcfg.StableNodeID
|
|
|
|
if err := tstest.WaitFor(5*time.Second, func() error {
|
|
st := n1.MustStatus()
|
|
if len(st.Peer) == 0 {
|
|
return errors.New("no peers")
|
|
}
|
|
if len(st.Peer) > 1 {
|
|
return fmt.Errorf("got %d peers; want 1", len(st.Peer))
|
|
}
|
|
peer := st.Peer[st.Peers()[0]]
|
|
peerStableID = peer.ID
|
|
if peer.ID == st.Self.ID {
|
|
return errors.New("peer is self")
|
|
}
|
|
|
|
if len(st.TailscaleIPs) == 0 {
|
|
return errors.New("no Tailscale IPs")
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
const timeout = 30 * time.Second
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
defer cancel()
|
|
|
|
c1 := n1.LocalClient()
|
|
c2 := n2.LocalClient()
|
|
|
|
wantNoWaitingFiles := func(c *local.Client) {
|
|
t.Helper()
|
|
files, err := c.WaitingFiles(ctx)
|
|
if err != nil {
|
|
t.Fatalf("WaitingFiles: %v", err)
|
|
}
|
|
if len(files) != 0 {
|
|
t.Fatalf("WaitingFiles: got %d files; want 0", len(files))
|
|
}
|
|
}
|
|
|
|
// Verify c2 has no files.
|
|
wantNoWaitingFiles(c2)
|
|
|
|
gotFile := make(chan bool, 1)
|
|
go func() {
|
|
v, err := c2.AwaitWaitingFiles(t.Context(), timeout)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(v) != 0 {
|
|
gotFile <- true
|
|
}
|
|
}()
|
|
|
|
fileContents := []byte("hello world this is a file")
|
|
|
|
n2ID := n2.MustStatus().Self.ID
|
|
t.Logf("n2 self.ID = %q; n1's peer[0].ID = %q", n2ID, peerStableID)
|
|
t.Logf("Doing PushFile ...")
|
|
err := c1.PushFile(ctx, n2.MustStatus().Self.ID, int64(len(fileContents)), "test.txt", bytes.NewReader(fileContents))
|
|
if err != nil {
|
|
t.Fatalf("PushFile from n1->n2: %v", err)
|
|
}
|
|
t.Logf("PushFile done")
|
|
|
|
select {
|
|
case <-gotFile:
|
|
t.Logf("n2 saw AwaitWaitingFiles wake up")
|
|
case <-ctx.Done():
|
|
t.Fatalf("n2 timeout waiting for AwaitWaitingFiles")
|
|
}
|
|
|
|
files, err := c2.WaitingFiles(ctx)
|
|
if err != nil {
|
|
t.Fatalf("c2.WaitingFiles: %v", err)
|
|
}
|
|
if len(files) != 1 {
|
|
t.Fatalf("c2.WaitingFiles: got %d files; want 1", len(files))
|
|
}
|
|
got := files[0]
|
|
want := apitype.WaitingFile{
|
|
Name: "test.txt",
|
|
Size: int64(len(fileContents)),
|
|
}
|
|
if got != want {
|
|
t.Fatalf("c2.WaitingFiles: got %+v; want %+v", got, want)
|
|
}
|
|
|
|
// Download the file.
|
|
rc, size, err := c2.GetWaitingFile(ctx, got.Name)
|
|
if err != nil {
|
|
t.Fatalf("c2.GetWaitingFile: %v", err)
|
|
}
|
|
if size != int64(len(fileContents)) {
|
|
t.Fatalf("c2.GetWaitingFile: got size %d; want %d", size, len(fileContents))
|
|
}
|
|
gotBytes, err := io.ReadAll(rc)
|
|
if err != nil {
|
|
t.Fatalf("c2.GetWaitingFile: %v", err)
|
|
}
|
|
if !bytes.Equal(gotBytes, fileContents) {
|
|
t.Fatalf("c2.GetWaitingFile: got %q; want %q", gotBytes, fileContents)
|
|
}
|
|
|
|
// Now delete it.
|
|
if err := c2.DeleteWaitingFile(ctx, got.Name); err != nil {
|
|
t.Fatalf("c2.DeleteWaitingFile: %v", err)
|
|
}
|
|
wantNoWaitingFiles(c2)
|
|
|
|
d1.MustCleanShutdown(t)
|
|
d2.MustCleanShutdown(t)
|
|
}
|