kubeadm: add test to detect panics when given certain feature gates

This integration test allows us to detect if a given feature gate will
panic kubeadm. This builds on the assumption that a golang panic makes
the process exit with the code 2.

These tests are not trying to check if the init process succeeds or
not, their only purpose is to ensure that the exit code of the
`kubeadm init` invocation is not 2, thus, reflecting a golang panic.

Some refactors had to be made to the test code, so we return the exit
code along with stdout and stderr.
This commit is contained in:
Rafael Fernández López 2019-10-01 20:37:37 +02:00
parent af67b2cbce
commit cd1ad5646e
No known key found for this signature in database
GPG Key ID: 8902294E78418CF9
6 changed files with 77 additions and 28 deletions

View File

@ -37,7 +37,7 @@ func TestCmdCompletion(t *testing.T) {
for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
_, _, actual := RunCmd(kubeadmPath, "completion", rt.args)
_, _, _, actual := RunCmd(kubeadmPath, "completion", rt.args)
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdCompletion running 'kubeadm completion %s' with an error: %v\n\texpected: %t\n\t actual: %t",

View File

@ -29,7 +29,7 @@ import (
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
)
func runKubeadmInit(args ...string) (string, string, error) {
func runKubeadmInit(args ...string) (string, string, int, error) {
kubeadmPath := getKubeadmPath()
kubeadmArgs := []string{"init", "--dry-run", "--ignore-preflight-errors=all"}
kubeadmArgs = append(kubeadmArgs, args...)
@ -66,7 +66,7 @@ func TestCmdInitToken(t *testing.T) {
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, err := runKubeadmInit(rt.args)
_, _, _, err := runKubeadmInit(rt.args)
if (err == nil) != rt.expected {
t.Fatalf(dedent.Dedent(`
CmdInitToken test case %q failed with an error: %v
@ -110,7 +110,7 @@ func TestCmdInitKubernetesVersion(t *testing.T) {
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, err := runKubeadmInit(rt.args)
_, _, _, err := runKubeadmInit(rt.args)
if (err == nil) != rt.expected {
t.Fatalf(dedent.Dedent(`
CmdInitKubernetesVersion test case %q failed with an error: %v
@ -184,7 +184,7 @@ func TestCmdInitConfig(t *testing.T) {
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, err := runKubeadmInit(rt.args)
_, _, _, err := runKubeadmInit(rt.args)
if (err == nil) != rt.expected {
t.Fatalf(dedent.Dedent(`
CmdInitConfig test case %q failed with an error: %v
@ -235,7 +235,7 @@ func TestCmdInitCertPhaseCSR(t *testing.T) {
csrDir := testutil.SetupTempDir(t)
cert := &certs.KubeadmCertKubeletClient
kubeadmPath := getKubeadmPath()
_, stderr, err := RunCmd(kubeadmPath,
_, stderr, _, err := RunCmd(kubeadmPath,
"init",
"phase",
"certs",
@ -303,7 +303,7 @@ func TestCmdInitAPIPort(t *testing.T) {
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, err := runKubeadmInit(rt.args)
_, _, _, err := runKubeadmInit(rt.args)
if (err == nil) != rt.expected {
t.Fatalf(dedent.Dedent(`
CmdInitAPIPort test case %q failed with an error: %v
@ -321,3 +321,52 @@ func TestCmdInitAPIPort(t *testing.T) {
})
}
}
// TestCmdInitFeatureGates test that feature gates won't make kubeadm panic.
// When go panics it will exit with a 2 code. While we don't expect the init
// calls to succeed in these tests, we ensure that the exit code of calling
// kubeadm with different feature gates is not 2.
func TestCmdInitFeatureGates(t *testing.T) {
const PanicExitcode = 2
if *kubeadmCmdSkip {
t.Log("kubeadm cmd tests being skipped")
t.Skip()
}
initTest := []struct {
name string
args string
}{
{
name: "no feature gates passed",
args: "",
},
{
name: "feature gate CoreDNS=true",
args: "--feature-gates=CoreDNS=true",
},
{
name: "feature gate IPv6DualStack=true",
args: "--feature-gates=IPv6DualStack=true",
},
}
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, exitcode, err := runKubeadmInit(rt.args)
if exitcode == PanicExitcode {
t.Fatalf(dedent.Dedent(`
CmdInitFeatureGates test case %q failed with an error: %v
command 'kubeadm init %s'
got exit code: %t (panic); unexpected
`),
rt.name,
err,
rt.args,
PanicExitcode,
)
}
})
}
}

View File

@ -21,7 +21,7 @@ import "testing"
// kubeadmReset executes "kubeadm reset" and restarts kubelet.
func kubeadmReset() error {
kubeadmPath := getKubeadmPath()
_, _, err := RunCmd(kubeadmPath, "reset")
_, _, _, err := RunCmd(kubeadmPath, "reset")
return err
}
@ -43,7 +43,7 @@ func TestCmdJoinConfig(t *testing.T) {
kubeadmPath := getKubeadmPath()
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdJoinConfig running 'kubeadm join %s' with an error: %v\n\texpected: %t\n\t actual: %t",
@ -76,7 +76,7 @@ func TestCmdJoinDiscoveryFile(t *testing.T) {
kubeadmPath := getKubeadmPath()
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdJoinDiscoveryFile running 'kubeadm join %s' with an error: %v\n\texpected: %t\n\t actual: %t",
@ -109,7 +109,7 @@ func TestCmdJoinDiscoveryToken(t *testing.T) {
kubeadmPath := getKubeadmPath()
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdJoinDiscoveryToken running 'kubeadm join %s' with an error: %v\n\texpected: %t\n\t actual: %t",
@ -141,7 +141,7 @@ func TestCmdJoinNodeName(t *testing.T) {
kubeadmPath := getKubeadmPath()
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdJoinNodeName running 'kubeadm join %s' with an error: %v\n\texpected: %t\n\t actual: %t",
@ -174,7 +174,7 @@ func TestCmdJoinTLSBootstrapToken(t *testing.T) {
kubeadmPath := getKubeadmPath()
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdJoinTLSBootstrapToken running 'kubeadm join %s' with an error: %v\n\texpected: %t\n\t actual: %t",
@ -207,7 +207,7 @@ func TestCmdJoinToken(t *testing.T) {
kubeadmPath := getKubeadmPath()
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdJoinToken running 'kubeadm join %s' with an error: %v\n\texpected: %t\n\t actual: %t",
@ -240,7 +240,7 @@ func TestCmdJoinBadArgs(t *testing.T) {
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdJoinBadArgs 'kubeadm join %s' with an error: %v\n\texpected: %t\n\t actual: %t",
@ -272,7 +272,7 @@ func TestCmdJoinArgsMixed(t *testing.T) {
kubeadmPath := getKubeadmPath()
for _, rt := range initTest {
t.Run(rt.name, func(t *testing.T) {
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdJoinArgsMixed running 'kubeadm join %s' with an error: %v\n\texpected: %t\n\t actual: %t",

View File

@ -48,7 +48,7 @@ func TestCmdTokenGenerate(t *testing.T) {
t.Log("kubeadm cmd tests being skipped")
t.Skip()
}
stdout, _, err := RunCmd(kubeadmPath, "token", "generate")
stdout, _, _, err := RunCmd(kubeadmPath, "token", "generate")
if err != nil {
t.Fatalf("'kubeadm token generate' exited uncleanly: %v", err)
}
@ -78,7 +78,7 @@ func TestCmdTokenGenerateTypoError(t *testing.T) {
}
kubeadmPath := getKubeadmPath()
_, _, err := RunCmd(kubeadmPath, "token", "genorate") // subtle typo
_, _, _, err := RunCmd(kubeadmPath, "token", "genorate") // subtle typo
if err == nil {
t.Error("'kubeadm token genorate' (a deliberate typo) exited without an error when we expected non-zero exit status")
}
@ -101,7 +101,7 @@ func TestCmdTokenDelete(t *testing.T) {
kubeadmPath := getKubeadmPath()
for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
_, _, actual := RunCmd(kubeadmPath, "token", "delete", rt.args)
_, _, _, actual := RunCmd(kubeadmPath, "token", "delete", rt.args)
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdTokenDelete running 'kubeadm token %s' with an error: %v\n\texpected: %t\n\t actual: %t",

View File

@ -29,24 +29,24 @@ import (
// Forked from test/e2e/framework because the e2e framework is quite bloated
// for our purposes here, and modified to remove undesired logging.
func runCmdNoWrap(command string, args ...string) (string, string, error) {
func runCmdNoWrap(command string, args ...string) (string, string, int, error) {
var bout, berr bytes.Buffer
cmd := exec.Command(command, args...)
cmd.Stdout = &bout
cmd.Stderr = &berr
err := cmd.Run()
stdout, stderr := bout.String(), berr.String()
return stdout, stderr, err
return stdout, stderr, cmd.ProcessState.ExitCode(), err
}
// RunCmd is a utility function for kubeadm testing that executes a specified command
func RunCmd(command string, args ...string) (string, string, error) {
stdout, stderr, err := runCmdNoWrap(command, args...)
func RunCmd(command string, args ...string) (string, string, int, error) {
stdout, stderr, retcode, err := runCmdNoWrap(command, args...)
if err != nil {
return stdout, stderr, errors.Wrapf(err, "error running %s %v; \nstdout %q, \nstderr %q, \ngot error",
command, args, stdout, stderr)
return stdout, stderr, retcode, errors.Wrapf(err, "error running %s %v; \nretcode %d, \nstdout %q, \nstderr %q, \ngot error",
command, args, retcode, stdout, stderr)
}
return stdout, stderr, nil
return stdout, stderr, retcode, nil
}
// RunSubCommand is a utility function for kubeadm testing that executes a Cobra sub command

View File

@ -53,7 +53,7 @@ func TestCmdVersion(t *testing.T) {
kubeadmPath := getKubeadmPath()
for _, rt := range versionTest {
t.Run(rt.name, func(t *testing.T) {
stdout, _, actual := RunCmd(kubeadmPath, "version", rt.args)
stdout, _, _, actual := RunCmd(kubeadmPath, "version", rt.args)
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdVersion running 'kubeadm version %s' with an error: %v\n\texpected: %t\n\t actual: %t",
@ -96,7 +96,7 @@ func TestCmdVersionOutputJsonOrYaml(t *testing.T) {
kubeadmPath := getKubeadmPath()
for _, rt := range versionTest {
t.Run(rt.name, func(t *testing.T) {
stdout, _, actual := RunCmd(kubeadmPath, "version", rt.args)
stdout, _, _, actual := RunCmd(kubeadmPath, "version", rt.args)
if (actual == nil) != rt.expected {
t.Errorf(
"failed CmdVersion running 'kubeadm version %s' with an error: %v\n\texpected: %t\n\t actual: %t",