integration

package
v0.27.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 11, 2025 License: BSD-3-Clause Imports: 54 Imported by: 0

README

Integration testing

Headscale relies on integration testing to ensure we remain compatible with Tailscale.

This is typically performed by starting a Headscale server and running a test "scenario" with an array of Tailscale clients and versions.

Headscale's test framework and the current set of scenarios are defined in this directory.

Tests are located in files ending with _test.go and the framework are located in the rest.

Running integration tests locally

The easiest way to run tests locally is to use act, a local GitHub Actions runner:

act pull_request -W .github/workflows/test-integration.yaml

Alternatively, the docker run command in each GitHub workflow file can be used.

Running integration tests on GitHub Actions

Each test currently runs as a separate workflows in GitHub actions, to add new test, run go generate inside ../cmd/gh-action-integration-generator/ and commit the result.

Documentation

Index

Constants

View Source
const (

	// TimestampFormat is the standard timestamp format used across all integration tests
	// Format: "2006-01-02T15-04-05.999999999" provides high precision timestamps
	// suitable for debugging and log correlation in integration tests.
	TimestampFormat = "2006-01-02T15-04-05.999999999"

	// TimestampFormatRunID is used for generating unique run identifiers
	// Format: "20060102-150405" provides compact date-time for file/directory names.
	TimestampFormatRunID = "20060102-150405"
)

Variables

View Source
var (

	// AllVersions represents a list of Tailscale versions the suite
	// uses to test compatibility with the ControlServer.
	//
	// The list contains two special cases, "head" and "unstable" which
	// points to the current tip of Tailscale's main branch and the latest
	// released unstable version.
	//
	// The rest of the version represents Tailscale versions that can be
	// found in Tailscale's apt repository.
	AllVersions = append([]string{"head", "unstable"}, capver.TailscaleLatestMajorMinor(10, true)...)

	// MustTestVersions is the minimum set of versions we should test.
	// At the moment, this is arbitrarily chosen as:
	//
	// - Two unstable (HEAD and unstable)
	// - Two latest versions
	// - Two oldest supported version.
	MustTestVersions = append(
		AllVersions[0:4],
		AllVersions[len(AllVersions)-2:]...,
	)
)

Functions

func GetUserByName added in v0.27.0

func GetUserByName(headscale ControlServer, username string) (*v1.User, error)

GetUserByName retrieves a user by name from the headscale server. This is a common pattern used when creating preauth keys or managing users.

func Webservice added in v0.26.0

func Webservice(s *Scenario, networkName string) (*dockertest.Resource, error)

Types

type ControlServer

type ControlServer interface {
	Shutdown() (string, string, error)
	SaveLog(string) (string, string, error)
	SaveProfile(string) error
	Execute(command []string) (string, error)
	WriteFile(path string, content []byte) error
	ConnectToNetwork(network *dockertest.Network) error
	GetHealthEndpoint() string
	GetEndpoint() string
	WaitForRunning() error
	CreateUser(user string) (*v1.User, error)
	CreateAuthKey(user uint64, reusable bool, ephemeral bool) (*v1.PreAuthKey, error)
	ListNodes(users ...string) ([]*v1.Node, error)
	DeleteNode(nodeID uint64) error
	NodesByUser() (map[string][]*v1.Node, error)
	NodesByName() (map[string]*v1.Node, error)
	ListUsers() ([]*v1.User, error)
	MapUsers() (map[string]*v1.User, error)
	ApproveRoutes(uint64, []netip.Prefix) (*v1.Node, error)
	GetCert() []byte
	GetHostname() string
	GetIPInNetwork(network *dockertest.Network) string
	SetPolicy(*policyv2.Policy) error
	GetAllMapReponses() (map[types.NodeID][]tailcfg.MapResponse, error)
	PrimaryRoutes() (*routes.DebugRoutes, error)
	DebugBatcher() (*hscontrol.DebugBatcherInfo, error)
	DebugNodeStore() (map[types.NodeID]types.Node, error)
	DebugFilter() ([]tailcfg.FilterRule, error)
}

type LoggingRoundTripper added in v0.26.0

type LoggingRoundTripper struct {
	Hostname string
}

func (LoggingRoundTripper) RoundTrip added in v0.26.0

func (t LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)

type NodeSystemStatus added in v0.27.0

type NodeSystemStatus struct {
	Batcher          bool
	BatcherConnCount int
	MapResponses     bool
	NodeStore        bool
}

NodeSystemStatus represents the status of a node across different systems

type Scenario

type Scenario struct {
	// contains filtered or unexported fields
}

Scenario is a representation of an environment with one ControlServer and one or more User's and its associated TailscaleClients. A Scenario is intended to simplify setting up a new testcase for testing a ControlServer with TailscaleClients. TODO(kradalby): make control server configurable, test correctness with Tailscale SaaS.

func NewScenario

func NewScenario(spec ScenarioSpec) (*Scenario, error)

NewScenario creates a test Scenario which can be used to bootstraps a ControlServer with a set of Users and TailscaleClients.

func (*Scenario) AddAndLoginClient added in v0.27.0

func (s *Scenario) AddAndLoginClient(
	t *testing.T,
	username string,
	version string,
	headscale ControlServer,
	tsOpts ...tsic.Option,
) (TailscaleClient, error)

AddAndLoginClient adds a new tailscale client to a user and logs it in. This combines the common pattern of: 1. Creating a new node 2. Finding the new node in the client list 3. Getting the user to create a preauth key 4. Logging in the new node

func (*Scenario) AddNetwork added in v0.26.0

func (s *Scenario) AddNetwork(name string) (*dockertest.Network, error)

func (*Scenario) CountTailscale

func (s *Scenario) CountTailscale() int

CountTailscale returns the total number of TailscaleClients in a Scenario. This is the sum of Users x TailscaleClients.

func (*Scenario) CreateDERPServer added in v0.24.0

func (s *Scenario) CreateDERPServer(version string, opts ...dsic.Option) (*dsic.DERPServerInContainer, error)

CreateDERPServer creates a new DERP server in a container.

func (*Scenario) CreateHeadscaleEnv

func (s *Scenario) CreateHeadscaleEnv(
	tsOpts []tsic.Option,
	opts ...hsic.Option,
) error

func (*Scenario) CreateHeadscaleEnvWithLoginURL added in v0.26.0

func (s *Scenario) CreateHeadscaleEnvWithLoginURL(
	tsOpts []tsic.Option,
	opts ...hsic.Option,
) error

func (*Scenario) CreatePreAuthKey

func (s *Scenario) CreatePreAuthKey(
	user uint64,
	reusable bool,
	ephemeral bool,
) (*v1.PreAuthKey, error)

CreatePreAuthKey creates a "pre authentorised key" to be created in the Headscale instance on behalf of the Scenario.

func (*Scenario) CreateTailscaleNode added in v0.25.0

func (s *Scenario) CreateTailscaleNode(
	version string,
	opts ...tsic.Option,
) (TailscaleClient, error)

func (*Scenario) CreateTailscaleNodesInUser added in v0.19.0

func (s *Scenario) CreateTailscaleNodesInUser(
	userStr string,
	requestedVersion string,
	count int,
	opts ...tsic.Option,
) error

CreateTailscaleNodesInUser creates and adds a new TailscaleClient to a User in the Scenario.

func (*Scenario) CreateUser added in v0.19.0

func (s *Scenario) CreateUser(user string) (*v1.User, error)

CreateUser creates a User to be created in the Headscale instance on behalf of the Scenario.

func (*Scenario) FindTailscaleClientByIP added in v0.21.0

func (s *Scenario) FindTailscaleClientByIP(ip netip.Addr) (TailscaleClient, error)

FindTailscaleClientByIP returns a TailscaleClient associated with an IP address if it exists.

func (*Scenario) GetClients

func (s *Scenario) GetClients(user string) ([]TailscaleClient, error)

GetClients returns all TailscaleClients associated with a User in a Scenario.

func (*Scenario) GetIPs

func (s *Scenario) GetIPs(user string) ([]netip.Addr, error)

GetIPs returns all netip.Addr of TailscaleClients associated with a User in a Scenario.

func (*Scenario) GetOrCreateUser added in v0.27.0

func (s *Scenario) GetOrCreateUser(userStr string) *User

GetOrCreateUser gets or creates a user in the scenario.

func (*Scenario) Headscale

func (s *Scenario) Headscale(opts ...hsic.Option) (ControlServer, error)

Headscale returns a ControlServer instance based on hsic (HeadscaleInContainer) If the Scenario already has an instance, the pointer to the running container will be return, otherwise a new instance will be created. TODO(kradalby): make port and headscale configurable, multiple instances support?

func (*Scenario) ListTailscaleClients

func (s *Scenario) ListTailscaleClients(users ...string) ([]TailscaleClient, error)

ListTailscaleClients returns a list of TailscaleClients given the Users passed as parameters.

func (*Scenario) ListTailscaleClientsFQDNs

func (s *Scenario) ListTailscaleClientsFQDNs(users ...string) ([]string, error)

ListTailscaleClientsFQDNs returns a list of FQDN based on Users passed as parameters.

func (*Scenario) ListTailscaleClientsIPs

func (s *Scenario) ListTailscaleClientsIPs(users ...string) ([]netip.Addr, error)

ListTailscaleClientsIPs returns a list of netip.Addr based on Users passed as parameters.

func (*Scenario) MustAddAndLoginClient added in v0.27.0

func (s *Scenario) MustAddAndLoginClient(
	t *testing.T,
	username string,
	version string,
	headscale ControlServer,
	tsOpts ...tsic.Option,
) TailscaleClient

MustAddAndLoginClient is like AddAndLoginClient but fails the test on error.

func (*Scenario) Network added in v0.26.0

func (s *Scenario) Network(name string) (*dockertest.Network, error)

func (*Scenario) Networks added in v0.26.0

func (s *Scenario) Networks() []*dockertest.Network

func (*Scenario) Pool added in v0.27.0

func (s *Scenario) Pool() *dockertest.Pool

Pool returns the dockertest pool for the scenario.

func (*Scenario) RunTailscaleUp

func (s *Scenario) RunTailscaleUp(
	userStr, loginServer, authKey string,
) error

RunTailscaleUp will log in all of the TailscaleClients associated with a User to the given ControlServer (by URL).

func (*Scenario) RunTailscaleUpWithURL added in v0.26.0

func (s *Scenario) RunTailscaleUpWithURL(userStr, loginServer string) error

func (*Scenario) Services added in v0.26.0

func (s *Scenario) Services(name string) ([]*dockertest.Resource, error)

func (*Scenario) Shutdown

func (s *Scenario) Shutdown()

Shutdown shuts down and cleans up all the containers (ControlServer, TailscaleClient) and networks associated with it. In addition, it will save the logs of the ControlServer to `/tmp/control` in the environment running the tests.

func (*Scenario) ShutdownAssertNoPanics added in v0.23.0

func (s *Scenario) ShutdownAssertNoPanics(t *testing.T)

func (*Scenario) SubnetOfNetwork added in v0.26.0

func (s *Scenario) SubnetOfNetwork(name string) (*netip.Prefix, error)

func (*Scenario) Users added in v0.19.0

func (s *Scenario) Users() []string

Users returns the name of all users associated with the Scenario.

func (*Scenario) WaitForTailscaleLogout added in v0.18.0

func (s *Scenario) WaitForTailscaleLogout() error

WaitForTailscaleLogout blocks execution until all TailscaleClients have logged out of the ControlServer.

func (*Scenario) WaitForTailscaleSync

func (s *Scenario) WaitForTailscaleSync() error

WaitForTailscaleSync blocks execution until all the TailscaleClient reports to have all other TailscaleClients present in their netmap.NetworkMap.

func (*Scenario) WaitForTailscaleSyncPerUser added in v0.27.0

func (s *Scenario) WaitForTailscaleSyncPerUser(timeout, retryInterval time.Duration) error

WaitForTailscaleSyncPerUser blocks execution until each TailscaleClient has the expected number of peers for its user. This is useful for policies like autogroup:self where nodes only see same-user peers, not all nodes in the network.

func (*Scenario) WaitForTailscaleSyncWithPeerCount added in v0.23.0

func (s *Scenario) WaitForTailscaleSyncWithPeerCount(peerCount int, timeout, retryInterval time.Duration) error

WaitForTailscaleSyncWithPeerCount blocks execution until all the TailscaleClient reports to have all other TailscaleClients present in their netmap.NetworkMap.

type ScenarioSpec added in v0.26.0

type ScenarioSpec struct {
	// Users is a list of usernames that will be created.
	// Each created user will get nodes equivalent to NodesPerUser
	Users []string

	// NodesPerUser is how many nodes should be attached to each user.
	NodesPerUser int

	// Networks, if set, is the separate Docker networks that should be
	// created and a list of the users that should be placed in those networks.
	// If not set, a single network will be created and all users+nodes will be
	// added there.
	// Please note that Docker networks are not necessarily routable and
	// connections between them might fall back to DERP.
	Networks map[string][]string

	// ExtraService, if set, is additional a map of network to additional
	// container services that should be set up. These container services
	// typically dont run Tailscale, e.g. web service to test subnet router.
	ExtraService map[string][]extraServiceFunc

	// Versions is specific list of versions to use for the test.
	Versions []string

	// OIDCUsers, if populated, will start a Mock OIDC server and populate
	// the user login stack with the given users.
	// If the NodesPerUser is set, it should align with this list to ensure
	// the correct users are logged in.
	// This is because the MockOIDC server can only serve login
	// requests based on a queue it has been given on startup.
	// We currently only populates it with one login request per user.
	OIDCUsers     []mockoidc.MockUser
	OIDCAccessTTL time.Duration

	MaxWait time.Duration
}

ScenarioSpec describes the users, nodes, and network topology to set up for a given scenario.

type TailscaleClient

type TailscaleClient interface {
	Hostname() string
	Shutdown() (string, string, error)
	Version() string
	Execute(
		command []string,
		options ...dockertestutil.ExecuteCommandOption,
	) (string, string, error)
	Login(loginServer, authKey string) error
	LoginWithURL(loginServer string) (*url.URL, error)
	Logout() error
	Restart() error
	Up() error
	Down() error
	IPs() ([]netip.Addr, error)
	MustIPs() []netip.Addr
	IPv4() (netip.Addr, error)
	MustIPv4() netip.Addr
	MustIPv6() netip.Addr
	FQDN() (string, error)
	MustFQDN() string
	Status(...bool) (*ipnstate.Status, error)
	MustStatus() *ipnstate.Status
	Netmap() (*netmap.NetworkMap, error)
	DebugDERPRegion(region string) (*ipnstate.DebugDERPRegionReport, error)
	GetNodePrivateKey() (*key.NodePrivate, error)
	Netcheck() (*netcheck.Report, error)
	WaitForNeedsLogin(timeout time.Duration) error
	WaitForRunning(timeout time.Duration) error
	WaitForPeers(expected int, timeout, retryInterval time.Duration) error
	Ping(hostnameOrIP string, opts ...tsic.PingOption) error
	Curl(url string, opts ...tsic.CurlOption) (string, error)
	CurlFailFast(url string) (string, error)
	Traceroute(netip.Addr) (util.Traceroute, error)
	ContainerID() string
	MustID() types.NodeID
	ReadFile(path string) ([]byte, error)
	PacketFilter() ([]filter.Match, error)

	// FailingPeersAsString returns a formatted-ish multi-line-string of peers in the client
	// and a bool indicating if the clients online count and peer count is equal.
	FailingPeersAsString() (string, bool, error)

	WriteLogs(stdout, stderr io.Writer) error
}

nolint

func FindNewClient added in v0.27.0

func FindNewClient(original, updated []TailscaleClient) (TailscaleClient, error)

FindNewClient finds a client that is in the new list but not in the original list. This is useful when dynamically adding nodes during tests and needing to identify which client was just added.

type User added in v0.19.0

type User struct {
	Clients map[string]TailscaleClient
	// contains filtered or unexported fields
}

User represents a User in the ControlServer and a map of TailscaleClient's associated with the User.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL