cmd/containerboot: refactor tests to have more explicit phases.

In preparation for making startup more complex with IPN bus watches.

Signed-off-by: David Anderson <danderson@tailscale.com>
pull/6646/head
David Anderson 2 years ago committed by Dave Anderson
parent e04aaa7575
commit e79a1eb24a

@ -33,6 +33,7 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tstest"
) )
func TestContainerBoot(t *testing.T) { func TestContainerBoot(t *testing.T) {
@ -97,31 +98,44 @@ func TestContainerBoot(t *testing.T) {
// step. Right now all of containerboot's modes either converge // step. Right now all of containerboot's modes either converge
// with no further interaction needed, or with one extra step // with no further interaction needed, or with one extra step
// only. // only.
tests := []struct { type phase struct {
Name string // Make LocalAPI report this status, then wait for the Wants below to be
Env map[string]string // satisfied. A zero Status is a valid state for a just-started
KubeSecret map[string]string // tailscaled.
WantArgs1 []string // Wait for containerboot to run these commands... Status ipnstate.Status
Status1 ipnstate.Status // ... then report this status in LocalAPI.
WantArgs2 []string // If non-nil, wait for containerboot to run these additional commands... // WantCmds is the commands that containerboot should run in this phase.
Status2 ipnstate.Status // ... then report this status in LocalAPI. WantCmds []string
// WantKubeSecret is the secret keys/values that should exist in the
// kube secret.
WantKubeSecret map[string]string WantKubeSecret map[string]string
WantFiles map[string]string // WantFiles files that should exist in the container and their
// contents.
WantFiles map[string]string
}
tests := []struct {
Name string
Env map[string]string
KubeSecret map[string]string
Phases []phase
}{ }{
{ {
// Out of the box default: runs in userspace mode, ephemeral storage, interactive login. // Out of the box default: runs in userspace mode, ephemeral storage, interactive login.
Name: "no_args", Name: "no_args",
Env: nil, Env: nil,
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking", {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false", WantCmds: []string{
}, "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking",
// The tailscale up call blocks until auth is complete, so "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false",
// by the time it returns the next converged state is },
// Running. },
Status1: ipnstate.Status{ {
BackendState: "Running", Status: ipnstate.Status{
TailscaleIPs: tsIPs, BackendState: "Running",
TailscaleIPs: tsIPs,
},
},
}, },
}, },
{ {
@ -130,13 +144,19 @@ func TestContainerBoot(t *testing.T) {
Env: map[string]string{ Env: map[string]string{
"TS_AUTH_KEY": "tskey-key", "TS_AUTH_KEY": "tskey-key",
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking", {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key", WantCmds: []string{
}, "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking",
Status1: ipnstate.Status{ "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key",
BackendState: "Running", },
TailscaleIPs: tsIPs, },
{
Status: ipnstate.Status{
BackendState: "Running",
TailscaleIPs: tsIPs,
},
},
}, },
}, },
{ {
@ -145,13 +165,19 @@ func TestContainerBoot(t *testing.T) {
"TS_AUTH_KEY": "tskey-key", "TS_AUTH_KEY": "tskey-key",
"TS_STATE_DIR": filepath.Join(d, "tmp"), "TS_STATE_DIR": filepath.Join(d, "tmp"),
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --statedir=/tmp --tun=userspace-networking", {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key", WantCmds: []string{
}, "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --statedir=/tmp --tun=userspace-networking",
Status1: ipnstate.Status{ "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key",
BackendState: "Running", },
TailscaleIPs: tsIPs, },
{
Status: ipnstate.Status{
BackendState: "Running",
TailscaleIPs: tsIPs,
},
},
}, },
}, },
{ {
@ -160,17 +186,23 @@ func TestContainerBoot(t *testing.T) {
"TS_AUTH_KEY": "tskey-key", "TS_AUTH_KEY": "tskey-key",
"TS_ROUTES": "1.2.3.0/24,10.20.30.0/24", "TS_ROUTES": "1.2.3.0/24,10.20.30.0/24",
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking", {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key --advertise-routes=1.2.3.0/24,10.20.30.0/24", WantCmds: []string{
}, "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking",
Status1: ipnstate.Status{ "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key --advertise-routes=1.2.3.0/24,10.20.30.0/24",
BackendState: "Running", },
TailscaleIPs: tsIPs, },
}, {
WantFiles: map[string]string{ Status: ipnstate.Status{
"proc/sys/net/ipv4/ip_forward": "0", BackendState: "Running",
"proc/sys/net/ipv6/conf/all/forwarding": "0", TailscaleIPs: tsIPs,
},
WantFiles: map[string]string{
"proc/sys/net/ipv4/ip_forward": "0",
"proc/sys/net/ipv6/conf/all/forwarding": "0",
},
},
}, },
}, },
{ {
@ -180,17 +212,23 @@ func TestContainerBoot(t *testing.T) {
"TS_ROUTES": "1.2.3.0/24,10.20.30.0/24", "TS_ROUTES": "1.2.3.0/24,10.20.30.0/24",
"TS_USERSPACE": "false", "TS_USERSPACE": "false",
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp", {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key --advertise-routes=1.2.3.0/24,10.20.30.0/24", WantCmds: []string{
}, "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp",
Status1: ipnstate.Status{ "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key --advertise-routes=1.2.3.0/24,10.20.30.0/24",
BackendState: "Running", },
TailscaleIPs: tsIPs, },
}, {
WantFiles: map[string]string{ Status: ipnstate.Status{
"proc/sys/net/ipv4/ip_forward": "1", BackendState: "Running",
"proc/sys/net/ipv6/conf/all/forwarding": "0", TailscaleIPs: tsIPs,
},
WantFiles: map[string]string{
"proc/sys/net/ipv4/ip_forward": "1",
"proc/sys/net/ipv6/conf/all/forwarding": "0",
},
},
}, },
}, },
{ {
@ -200,17 +238,23 @@ func TestContainerBoot(t *testing.T) {
"TS_ROUTES": "::/64,1::/64", "TS_ROUTES": "::/64,1::/64",
"TS_USERSPACE": "false", "TS_USERSPACE": "false",
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp", {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key --advertise-routes=::/64,1::/64", WantCmds: []string{
}, "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp",
Status1: ipnstate.Status{ "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key --advertise-routes=::/64,1::/64",
BackendState: "Running", },
TailscaleIPs: tsIPs, },
}, {
WantFiles: map[string]string{ Status: ipnstate.Status{
"proc/sys/net/ipv4/ip_forward": "0", BackendState: "Running",
"proc/sys/net/ipv6/conf/all/forwarding": "1", TailscaleIPs: tsIPs,
},
WantFiles: map[string]string{
"proc/sys/net/ipv4/ip_forward": "0",
"proc/sys/net/ipv6/conf/all/forwarding": "1",
},
},
}, },
}, },
{ {
@ -220,17 +264,23 @@ func TestContainerBoot(t *testing.T) {
"TS_ROUTES": "::/64,1.2.3.0/24", "TS_ROUTES": "::/64,1.2.3.0/24",
"TS_USERSPACE": "false", "TS_USERSPACE": "false",
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp", {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key --advertise-routes=::/64,1.2.3.0/24", WantCmds: []string{
}, "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp",
Status1: ipnstate.Status{ "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key --advertise-routes=::/64,1.2.3.0/24",
BackendState: "Running", },
TailscaleIPs: tsIPs, },
}, {
WantFiles: map[string]string{ Status: ipnstate.Status{
"proc/sys/net/ipv4/ip_forward": "1", BackendState: "Running",
"proc/sys/net/ipv6/conf/all/forwarding": "1", TailscaleIPs: tsIPs,
},
WantFiles: map[string]string{
"proc/sys/net/ipv4/ip_forward": "1",
"proc/sys/net/ipv6/conf/all/forwarding": "1",
},
},
}, },
}, },
{ {
@ -240,20 +290,22 @@ func TestContainerBoot(t *testing.T) {
"TS_DEST_IP": "1.2.3.4", "TS_DEST_IP": "1.2.3.4",
"TS_USERSPACE": "false", "TS_USERSPACE": "false",
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp", {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key", WantCmds: []string{
}, "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp",
Status1: ipnstate.Status{ "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key",
BackendState: "Running", },
TailscaleIPs: tsIPs, },
}, {
WantArgs2: []string{ Status: ipnstate.Status{
"/usr/bin/iptables -t nat -I PREROUTING 1 -d 100.64.0.1 -j DNAT --to-destination 1.2.3.4", BackendState: "Running",
}, TailscaleIPs: tsIPs,
Status2: ipnstate.Status{ },
BackendState: "Running", WantCmds: []string{
TailscaleIPs: tsIPs, "/usr/bin/iptables -t nat -I PREROUTING 1 -d 100.64.0.1 -j DNAT --to-destination 1.2.3.4",
},
},
}, },
}, },
{ {
@ -262,18 +314,26 @@ func TestContainerBoot(t *testing.T) {
"TS_AUTH_KEY": "tskey-key", "TS_AUTH_KEY": "tskey-key",
"TS_AUTH_ONCE": "true", "TS_AUTH_ONCE": "true",
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking", {
}, WantCmds: []string{
Status1: ipnstate.Status{ "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking",
BackendState: "NeedsLogin", },
}, },
WantArgs2: []string{ {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key", Status: ipnstate.Status{
}, BackendState: "NeedsLogin",
Status2: ipnstate.Status{ },
BackendState: "Running", WantCmds: []string{
TailscaleIPs: tsIPs, "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key",
},
},
{
Status: ipnstate.Status{
BackendState: "Running",
TailscaleIPs: tsIPs,
},
},
}, },
}, },
{ {
@ -285,20 +345,29 @@ func TestContainerBoot(t *testing.T) {
KubeSecret: map[string]string{ KubeSecret: map[string]string{
"authkey": "tskey-key", "authkey": "tskey-key",
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=kube:tailscale --statedir=/tmp --tun=userspace-networking", {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key", WantCmds: []string{
}, "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=kube:tailscale --statedir=/tmp --tun=userspace-networking",
Status1: ipnstate.Status{ "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key",
BackendState: "Running", },
TailscaleIPs: tsIPs, WantKubeSecret: map[string]string{
Self: &ipnstate.PeerStatus{ "authkey": "tskey-key",
ID: tailcfg.StableNodeID("myID"), },
},
{
Status: ipnstate.Status{
BackendState: "Running",
TailscaleIPs: tsIPs,
Self: &ipnstate.PeerStatus{
ID: tailcfg.StableNodeID("myID"),
},
},
WantKubeSecret: map[string]string{
"authkey": "tskey-key",
"device_id": "myID",
},
}, },
},
WantKubeSecret: map[string]string{
"authkey": "tskey-key",
"device_id": "myID",
}, },
}, },
{ {
@ -312,24 +381,38 @@ func TestContainerBoot(t *testing.T) {
KubeSecret: map[string]string{ KubeSecret: map[string]string{
"authkey": "tskey-key", "authkey": "tskey-key",
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=kube:tailscale --statedir=/tmp --tun=userspace-networking", {
}, WantCmds: []string{
Status1: ipnstate.Status{ "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=kube:tailscale --statedir=/tmp --tun=userspace-networking",
BackendState: "NeedsLogin", },
}, WantKubeSecret: map[string]string{
WantArgs2: []string{ "authkey": "tskey-key",
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key", },
}, },
Status2: ipnstate.Status{ {
BackendState: "Running", Status: ipnstate.Status{
TailscaleIPs: tsIPs, BackendState: "NeedsLogin",
Self: &ipnstate.PeerStatus{ },
ID: tailcfg.StableNodeID("myID"), WantCmds: []string{
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key",
},
WantKubeSecret: map[string]string{
"authkey": "tskey-key",
},
},
{
Status: ipnstate.Status{
BackendState: "Running",
TailscaleIPs: tsIPs,
Self: &ipnstate.PeerStatus{
ID: tailcfg.StableNodeID("myID"),
},
},
WantKubeSecret: map[string]string{
"device_id": "myID",
},
}, },
},
WantKubeSecret: map[string]string{
"device_id": "myID",
}, },
}, },
{ {
@ -338,16 +421,22 @@ func TestContainerBoot(t *testing.T) {
"TS_SOCKS5_SERVER": "localhost:1080", "TS_SOCKS5_SERVER": "localhost:1080",
"TS_OUTBOUND_HTTP_PROXY_LISTEN": "localhost:8080", "TS_OUTBOUND_HTTP_PROXY_LISTEN": "localhost:8080",
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking --socks5-server=localhost:1080 --outbound-http-proxy-listen=localhost:8080", {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false", WantCmds: []string{
}, "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking --socks5-server=localhost:1080 --outbound-http-proxy-listen=localhost:8080",
// The tailscale up call blocks until auth is complete, so "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false",
// by the time it returns the next converged state is },
// Running. },
Status1: ipnstate.Status{ {
BackendState: "Running", // The tailscale up call blocks until auth is complete, so
TailscaleIPs: tsIPs, // by the time it returns the next converged state is
// Running.
Status: ipnstate.Status{
BackendState: "Running",
TailscaleIPs: tsIPs,
},
},
}, },
}, },
{ {
@ -355,13 +444,19 @@ func TestContainerBoot(t *testing.T) {
Env: map[string]string{ Env: map[string]string{
"TS_ACCEPT_DNS": "true", "TS_ACCEPT_DNS": "true",
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking", {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=true", WantCmds: []string{
}, "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking",
Status1: ipnstate.Status{ "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=true",
BackendState: "Running", },
TailscaleIPs: tsIPs, },
{
Status: ipnstate.Status{
BackendState: "Running",
TailscaleIPs: tsIPs,
},
},
}, },
}, },
{ {
@ -370,13 +465,18 @@ func TestContainerBoot(t *testing.T) {
"TS_EXTRA_ARGS": "--widget=rotated", "TS_EXTRA_ARGS": "--widget=rotated",
"TS_TAILSCALED_EXTRA_ARGS": "--experiments=widgets", "TS_TAILSCALED_EXTRA_ARGS": "--experiments=widgets",
}, },
WantArgs1: []string{ Phases: []phase{
"/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking --experiments=widgets", {
"/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --widget=rotated", WantCmds: []string{
}, "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking --experiments=widgets",
Status1: ipnstate.Status{ "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --widget=rotated",
BackendState: "Running", },
TailscaleIPs: tsIPs, }, {
Status: ipnstate.Status{
BackendState: "Running",
TailscaleIPs: tsIPs,
},
},
}, },
}, },
} }
@ -419,35 +519,45 @@ func TestContainerBoot(t *testing.T) {
cmd.Process.Wait() cmd.Process.Wait()
}() }()
waitArgs(t, 2*time.Second, d, argFile, strings.Join(test.WantArgs1, "\n")) var wantCmds []string
lapi.SetStatus(test.Status1) for _, p := range test.Phases {
if test.WantArgs2 != nil { lapi.SetStatus(p.Status)
waitArgs(t, 2*time.Second, d, argFile, strings.Join(append(test.WantArgs1, test.WantArgs2...), "\n")) wantCmds = append(wantCmds, p.WantCmds...)
lapi.SetStatus(test.Status2) waitArgs(t, 2*time.Second, d, argFile, strings.Join(wantCmds, "\n"))
} err := tstest.WaitFor(2*time.Second, func() error {
waitLogLine(t, 2*time.Second, cbOut, "Startup complete, waiting for shutdown signal") if p.WantKubeSecret != nil {
got := kube.Secret()
if test.WantKubeSecret != nil { if diff := cmp.Diff(got, p.WantKubeSecret); diff != "" {
got := kube.Secret() return fmt.Errorf("unexpected kube secret data (-got+want):\n%s", diff)
if diff := cmp.Diff(got, test.WantKubeSecret); diff != "" { }
t.Fatalf("unexpected kube secret data (-got+want):\n%s", diff) } else {
} got := kube.Secret()
} else { if len(got) > 0 {
got := kube.Secret() return fmt.Errorf("kube secret unexpectedly not empty, got %#v", got)
if len(got) != 0 { }
t.Fatalf("kube secret unexpectedly not empty, got %#v", got) }
} return nil
} })
for path, want := range test.WantFiles {
gotBs, err := os.ReadFile(filepath.Join(d, path))
if err != nil { if err != nil {
t.Fatalf("reading wanted file %q: %v", path, err) t.Fatal(err)
} }
if got := strings.TrimSpace(string(gotBs)); got != want { err = tstest.WaitFor(2*time.Second, func() error {
t.Errorf("wrong file contents for %q, got %q want %q", path, got, want) for path, want := range p.WantFiles {
gotBs, err := os.ReadFile(filepath.Join(d, path))
if err != nil {
return fmt.Errorf("reading wanted file %q: %v", path, err)
}
if got := strings.TrimSpace(string(gotBs)); got != want {
return fmt.Errorf("wrong file contents for %q, got %q want %q", path, got, want)
}
}
return nil
})
if err != nil {
t.Fatal(err)
} }
} }
waitLogLine(t, 2*time.Second, cbOut, "Startup complete, waiting for shutdown signal")
}) })
} }
} }

Loading…
Cancel
Save