Handle errors in integration test setups

Thanks @kev-the-dev

Closes #1460

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
Kristoffer Dalby 2023-08-29 08:33:33 +02:00 committed by Kristoffer Dalby
parent 63caf9a222
commit b4a4d0f760
55 changed files with 829 additions and 1118 deletions

View file

@ -34,9 +34,14 @@ var (
errTailscaleWrongPeerCount = errors.New("wrong peer count")
errTailscaleCannotUpWithoutAuthkey = errors.New("cannot up without authkey")
errTailscaleNotConnected = errors.New("tailscale not connected")
errTailscaledNotReadyForLogin = errors.New("tailscaled not ready for login")
errTailscaleNotLoggedOut = errors.New("tailscale not logged out")
)
func errTailscaleStatus(hostname string, err error) error {
return fmt.Errorf("%s failed to fetch tailscale status: %w", hostname, err)
}
// TailscaleInContainer is an implementation of TailscaleClient which
// sets up a Tailscale instance inside a container.
type TailscaleInContainer struct {
@ -165,7 +170,7 @@ func New(
network: network,
withEntrypoint: []string{
"/bin/bash",
"/bin/sh",
"-c",
"/bin/sleep 3 ; update-ca-certificates ; tailscaled --tun=tsdev",
},
@ -204,16 +209,48 @@ func New(
return nil, err
}
container, err := pool.BuildAndRunWithBuildOptions(
createTailscaleBuildOptions(version),
tailscaleOptions,
dockertestutil.DockerRestartPolicy,
dockertestutil.DockerAllowLocalIPv6,
dockertestutil.DockerAllowNetworkAdministration,
)
var container *dockertest.Resource
switch version {
case "head":
buildOptions := &dockertest.BuildOptions{
Dockerfile: "Dockerfile.tailscale-HEAD",
ContextDir: dockerContextPath,
BuildArgs: []docker.BuildArg{},
}
container, err = pool.BuildAndRunWithBuildOptions(
buildOptions,
tailscaleOptions,
dockertestutil.DockerRestartPolicy,
dockertestutil.DockerAllowLocalIPv6,
dockertestutil.DockerAllowNetworkAdministration,
)
case "unstable":
tailscaleOptions.Repository = "tailscale/tailscale"
tailscaleOptions.Tag = version
container, err = pool.RunWithOptions(
tailscaleOptions,
dockertestutil.DockerRestartPolicy,
dockertestutil.DockerAllowLocalIPv6,
dockertestutil.DockerAllowNetworkAdministration,
)
default:
tailscaleOptions.Repository = "tailscale/tailscale"
tailscaleOptions.Tag = "v" + version
container, err = pool.RunWithOptions(
tailscaleOptions,
dockertestutil.DockerRestartPolicy,
dockertestutil.DockerAllowLocalIPv6,
dockertestutil.DockerAllowNetworkAdministration,
)
}
if err != nil {
return nil, fmt.Errorf(
"could not start tailscale container (version: %s): %w",
"%s could not start tailscale container (version: %s): %w",
hostname,
version,
err,
)
@ -270,7 +307,7 @@ func (t *TailscaleInContainer) Execute(
options...,
)
if err != nil {
log.Printf("command stderr: %s\n", stderr)
// log.Printf("command stderr: %s\n", stderr)
if stdout != "" {
log.Printf("command stdout: %s\n", stdout)
@ -288,18 +325,15 @@ func (t *TailscaleInContainer) Execute(
// Up runs the login routine on the given Tailscale instance.
// This login mechanism uses the authorised key for authentication.
func (t *TailscaleInContainer) Up(
func (t *TailscaleInContainer) Login(
loginServer, authKey string,
) error {
command := []string{
"tailscale",
"up",
"-login-server",
loginServer,
"--authkey",
authKey,
"--hostname",
t.hostname,
"--login-server=" + loginServer,
"--authkey=" + authKey,
"--hostname=" + t.hostname,
}
if t.withSSH {
@ -313,7 +347,12 @@ func (t *TailscaleInContainer) Up(
}
if _, _, err := t.Execute(command); err != nil {
return fmt.Errorf("failed to join tailscale client: %w", err)
return fmt.Errorf(
"%s failed to join tailscale client (%s): %w",
t.hostname,
strings.Join(command, " "),
err,
)
}
return nil
@ -321,16 +360,14 @@ func (t *TailscaleInContainer) Up(
// Up runs the login routine on the given Tailscale instance.
// This login mechanism uses web + command line flow for authentication.
func (t *TailscaleInContainer) UpWithLoginURL(
func (t *TailscaleInContainer) LoginWithURL(
loginServer string,
) (*url.URL, error) {
command := []string{
"tailscale",
"up",
"-login-server",
loginServer,
"--hostname",
t.hostname,
"--login-server=" + loginServer,
"--hostname=" + t.hostname,
}
_, stderr, err := t.Execute(command)
@ -378,7 +415,7 @@ func (t *TailscaleInContainer) IPs() ([]netip.Addr, error) {
result, _, err := t.Execute(command)
if err != nil {
return []netip.Addr{}, fmt.Errorf("failed to join tailscale client: %w", err)
return []netip.Addr{}, fmt.Errorf("%s failed to join tailscale client: %w", t.hostname, err)
}
for _, address := range strings.Split(result, "\n") {
@ -432,19 +469,37 @@ func (t *TailscaleInContainer) FQDN() (string, error) {
return status.Self.DNSName, nil
}
// WaitForReady blocks until the Tailscale (tailscaled) instance is ready
// to login or be used.
func (t *TailscaleInContainer) WaitForReady() error {
// WaitForNeedsLogin blocks until the Tailscale (tailscaled) instance has
// started and needs to be logged into.
func (t *TailscaleInContainer) WaitForNeedsLogin() error {
return t.pool.Retry(func() error {
status, err := t.Status()
if err != nil {
return fmt.Errorf("failed to fetch tailscale status: %w", err)
return errTailscaleStatus(t.hostname, err)
}
if status.CurrentTailnet != nil {
// ipnstate.Status.CurrentTailnet was added in Tailscale 1.22.0
// https://github.com/tailscale/tailscale/pull/3865
//
// Before that, we can check the BackendState to see if the
// tailscaled daemon is connected to the control system.
if status.BackendState == "NeedsLogin" {
return nil
}
return errTailscaledNotReadyForLogin
})
}
// WaitForRunning blocks until the Tailscale (tailscaled) instance is logged in
// and ready to be used.
func (t *TailscaleInContainer) WaitForRunning() error {
return t.pool.Retry(func() error {
status, err := t.Status()
if err != nil {
return errTailscaleStatus(t.hostname, err)
}
// ipnstate.Status.CurrentTailnet was added in Tailscale 1.22.0
// https://github.com/tailscale/tailscale/pull/3865
//
@ -460,10 +515,10 @@ func (t *TailscaleInContainer) WaitForReady() error {
// WaitForLogout blocks until the Tailscale instance has logged out.
func (t *TailscaleInContainer) WaitForLogout() error {
return t.pool.Retry(func() error {
return fmt.Errorf("%s err: %w", t.hostname, t.pool.Retry(func() error {
status, err := t.Status()
if err != nil {
return fmt.Errorf("failed to fetch tailscale status: %w", err)
return errTailscaleStatus(t.hostname, err)
}
if status.CurrentTailnet == nil {
@ -471,7 +526,7 @@ func (t *TailscaleInContainer) WaitForLogout() error {
}
return errTailscaleNotLoggedOut
})
}))
}
// WaitForPeers blocks until N number of peers is present in the
@ -480,11 +535,17 @@ func (t *TailscaleInContainer) WaitForPeers(expected int) error {
return t.pool.Retry(func() error {
status, err := t.Status()
if err != nil {
return fmt.Errorf("failed to fetch tailscale status: %w", err)
return errTailscaleStatus(t.hostname, err)
}
if peers := status.Peers(); len(peers) != expected {
return errTailscaleWrongPeerCount
return fmt.Errorf(
"%s err: %w expected %d, got %d",
t.hostname,
errTailscaleWrongPeerCount,
expected,
len(peers),
)
}
return nil
@ -683,47 +744,3 @@ func (t *TailscaleInContainer) Curl(url string, opts ...CurlOption) (string, err
func (t *TailscaleInContainer) WriteFile(path string, data []byte) error {
return integrationutil.WriteFileToContainer(t.pool, t.container, path, data)
}
func createTailscaleBuildOptions(version string) *dockertest.BuildOptions {
var tailscaleBuildOptions *dockertest.BuildOptions
switch version {
case "head":
tailscaleBuildOptions = &dockertest.BuildOptions{
Dockerfile: "Dockerfile.tailscale-HEAD",
ContextDir: dockerContextPath,
BuildArgs: []docker.BuildArg{},
}
case "unstable":
tailscaleBuildOptions = &dockertest.BuildOptions{
Dockerfile: "Dockerfile.tailscale",
ContextDir: dockerContextPath,
BuildArgs: []docker.BuildArg{
{
Name: "TAILSCALE_VERSION",
Value: "*", // Installs the latest version https://askubuntu.com/a/824926
},
{
Name: "TAILSCALE_CHANNEL",
Value: "unstable",
},
},
}
default:
tailscaleBuildOptions = &dockertest.BuildOptions{
Dockerfile: "Dockerfile.tailscale",
ContextDir: dockerContextPath,
BuildArgs: []docker.BuildArg{
{
Name: "TAILSCALE_VERSION",
Value: version,
},
{
Name: "TAILSCALE_CHANNEL",
Value: "stable",
},
},
}
}
return tailscaleBuildOptions
}