mirror of
https://github.com/juanfont/headscale.git
synced 2025-10-19 11:15:48 +02:00
This PR addresses some consistency issues that was introduced or discovered with the nodestore. nodestore: Now returns the node that is being put or updated when it is finished. This closes a race condition where when we read it back, we do not necessarily get the node with the given change and it ensures we get all the other updates from that batch write. auth: Authentication paths have been unified and simplified. It removes a lot of bad branches and ensures we only do the minimal work. A comprehensive auth test set has been created so we do not have to run integration tests to validate auth and it has allowed us to generate test cases for all the branches we currently know of. integration: added a lot more tooling and checks to validate that nodes reach the expected state when they come up and down. Standardised between the different auth models. A lot of this is to support or detect issues in the changes to nodestore (races) and auth (inconsistencies after login and reaching correct state) This PR was assisted, particularly tests, by claude code.
155 lines
3.7 KiB
Go
155 lines
3.7 KiB
Go
package integration
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/juanfont/headscale/integration/dockertestutil"
|
|
"github.com/juanfont/headscale/integration/tsic"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// This file is intended to "test the test framework", by proxy it will also test
|
|
// some Headscale/Tailscale stuff, but mostly in very simple ways.
|
|
|
|
func IntegrationSkip(t *testing.T) {
|
|
t.Helper()
|
|
|
|
if !dockertestutil.IsRunningInContainer() {
|
|
t.Skip("not running in docker, skipping")
|
|
}
|
|
|
|
if testing.Short() {
|
|
t.Skip("skipping integration tests due to short flag")
|
|
}
|
|
}
|
|
|
|
// If subtests are parallel, then they will start before setup is run.
|
|
// This might mean we approach setup slightly wrong, but for now, ignore
|
|
// the linter
|
|
// nolint:tparallel
|
|
func TestHeadscale(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
|
|
var err error
|
|
|
|
user := "test-space"
|
|
|
|
scenario, err := NewScenario(ScenarioSpec{})
|
|
require.NoError(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
t.Run("start-headscale", func(t *testing.T) {
|
|
headscale, err := scenario.Headscale()
|
|
if err != nil {
|
|
t.Fatalf("failed to create start headcale: %s", err)
|
|
}
|
|
|
|
err = headscale.WaitForRunning()
|
|
if err != nil {
|
|
t.Fatalf("headscale failed to become ready: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("create-user", func(t *testing.T) {
|
|
_, err := scenario.CreateUser(user)
|
|
if err != nil {
|
|
t.Fatalf("failed to create user: %s", err)
|
|
}
|
|
|
|
if _, ok := scenario.users[user]; !ok {
|
|
t.Fatalf("user is not in scenario")
|
|
}
|
|
})
|
|
|
|
t.Run("create-auth-key", func(t *testing.T) {
|
|
_, err := scenario.CreatePreAuthKey(1, true, false)
|
|
if err != nil {
|
|
t.Fatalf("failed to create preauthkey: %s", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// If subtests are parallel, then they will start before setup is run.
|
|
// This might mean we approach setup slightly wrong, but for now, ignore
|
|
// the linter
|
|
// nolint:tparallel
|
|
func TestTailscaleNodesJoiningHeadcale(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
|
|
var err error
|
|
|
|
user := "join-node-test"
|
|
|
|
count := 1
|
|
|
|
scenario, err := NewScenario(ScenarioSpec{})
|
|
require.NoError(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
t.Run("start-headscale", func(t *testing.T) {
|
|
headscale, err := scenario.Headscale()
|
|
if err != nil {
|
|
t.Fatalf("failed to create start headcale: %s", err)
|
|
}
|
|
|
|
err = headscale.WaitForRunning()
|
|
if err != nil {
|
|
t.Fatalf("headscale failed to become ready: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("create-user", func(t *testing.T) {
|
|
_, err := scenario.CreateUser(user)
|
|
if err != nil {
|
|
t.Fatalf("failed to create user: %s", err)
|
|
}
|
|
|
|
if _, ok := scenario.users[user]; !ok {
|
|
t.Fatalf("user is not in scenario")
|
|
}
|
|
})
|
|
|
|
t.Run("create-tailscale", func(t *testing.T) {
|
|
err := scenario.CreateTailscaleNodesInUser(user, "unstable", count, tsic.WithNetwork(scenario.networks[scenario.testDefaultNetwork]))
|
|
if err != nil {
|
|
t.Fatalf("failed to add tailscale nodes: %s", err)
|
|
}
|
|
|
|
if clients := len(scenario.users[user].Clients); clients != count {
|
|
t.Fatalf("wrong number of tailscale clients: %d != %d", clients, count)
|
|
}
|
|
})
|
|
|
|
t.Run("join-headscale", func(t *testing.T) {
|
|
key, err := scenario.CreatePreAuthKey(1, true, false)
|
|
if err != nil {
|
|
t.Fatalf("failed to create preauthkey: %s", err)
|
|
}
|
|
|
|
headscale, err := scenario.Headscale()
|
|
if err != nil {
|
|
t.Fatalf("failed to create start headcale: %s", err)
|
|
}
|
|
|
|
err = scenario.RunTailscaleUp(
|
|
user,
|
|
headscale.GetEndpoint(),
|
|
key.GetKey(),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("failed to login: %s", err)
|
|
}
|
|
})
|
|
|
|
t.Run("get-ips", func(t *testing.T) {
|
|
ips, err := scenario.GetIPs(user)
|
|
if err != nil {
|
|
t.Fatalf("failed to get tailscale ips: %s", err)
|
|
}
|
|
|
|
if len(ips) != count*2 {
|
|
t.Fatalf("got the wrong amount of tailscale ips, %d != %d", len(ips), count*2)
|
|
}
|
|
})
|
|
}
|