From cd1ad5646e8a9c0b1e4e2440fef3e202563a0f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Fern=C3=A1ndez=20L=C3=B3pez?= Date: Tue, 1 Oct 2019 20:37:37 +0200 Subject: [PATCH] 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. --- cmd/kubeadm/test/cmd/completion_test.go | 2 +- cmd/kubeadm/test/cmd/init_test.go | 61 ++++++++++++++++++++++--- cmd/kubeadm/test/cmd/join_test.go | 18 ++++---- cmd/kubeadm/test/cmd/token_test.go | 6 +-- cmd/kubeadm/test/cmd/util.go | 14 +++--- cmd/kubeadm/test/cmd/version_test.go | 4 +- 6 files changed, 77 insertions(+), 28 deletions(-) diff --git a/cmd/kubeadm/test/cmd/completion_test.go b/cmd/kubeadm/test/cmd/completion_test.go index 95fe79d8924..d5dc7bd5179 100644 --- a/cmd/kubeadm/test/cmd/completion_test.go +++ b/cmd/kubeadm/test/cmd/completion_test.go @@ -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", diff --git a/cmd/kubeadm/test/cmd/init_test.go b/cmd/kubeadm/test/cmd/init_test.go index 1cae76ac246..4aab86c195d 100644 --- a/cmd/kubeadm/test/cmd/init_test.go +++ b/cmd/kubeadm/test/cmd/init_test.go @@ -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, + ) + } + }) + } +} diff --git a/cmd/kubeadm/test/cmd/join_test.go b/cmd/kubeadm/test/cmd/join_test.go index 54b59901f3c..e4c8efdbce2 100644 --- a/cmd/kubeadm/test/cmd/join_test.go +++ b/cmd/kubeadm/test/cmd/join_test.go @@ -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", diff --git a/cmd/kubeadm/test/cmd/token_test.go b/cmd/kubeadm/test/cmd/token_test.go index f24b936c6c1..7d61a519db1 100644 --- a/cmd/kubeadm/test/cmd/token_test.go +++ b/cmd/kubeadm/test/cmd/token_test.go @@ -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", diff --git a/cmd/kubeadm/test/cmd/util.go b/cmd/kubeadm/test/cmd/util.go index ecc1951b5dd..babf6684a90 100644 --- a/cmd/kubeadm/test/cmd/util.go +++ b/cmd/kubeadm/test/cmd/util.go @@ -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 diff --git a/cmd/kubeadm/test/cmd/version_test.go b/cmd/kubeadm/test/cmd/version_test.go index 320bb48249c..98511b6aa6a 100644 --- a/cmd/kubeadm/test/cmd/version_test.go +++ b/cmd/kubeadm/test/cmd/version_test.go @@ -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",