Skip to content

Commit cd1ad56

Browse files
committed
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.
1 parent af67b2c commit cd1ad56

File tree

6 files changed

+77
-28
lines changed

6 files changed

+77
-28
lines changed

cmd/kubeadm/test/cmd/completion_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestCmdCompletion(t *testing.T) {
3737

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

cmd/kubeadm/test/cmd/init_test.go

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
3030
)
3131

32-
func runKubeadmInit(args ...string) (string, string, error) {
32+
func runKubeadmInit(args ...string) (string, string, int, error) {
3333
kubeadmPath := getKubeadmPath()
3434
kubeadmArgs := []string{"init", "--dry-run", "--ignore-preflight-errors=all"}
3535
kubeadmArgs = append(kubeadmArgs, args...)
@@ -66,7 +66,7 @@ func TestCmdInitToken(t *testing.T) {
6666

6767
for _, rt := range initTest {
6868
t.Run(rt.name, func(t *testing.T) {
69-
_, _, err := runKubeadmInit(rt.args)
69+
_, _, _, err := runKubeadmInit(rt.args)
7070
if (err == nil) != rt.expected {
7171
t.Fatalf(dedent.Dedent(`
7272
CmdInitToken test case %q failed with an error: %v
@@ -110,7 +110,7 @@ func TestCmdInitKubernetesVersion(t *testing.T) {
110110

111111
for _, rt := range initTest {
112112
t.Run(rt.name, func(t *testing.T) {
113-
_, _, err := runKubeadmInit(rt.args)
113+
_, _, _, err := runKubeadmInit(rt.args)
114114
if (err == nil) != rt.expected {
115115
t.Fatalf(dedent.Dedent(`
116116
CmdInitKubernetesVersion test case %q failed with an error: %v
@@ -184,7 +184,7 @@ func TestCmdInitConfig(t *testing.T) {
184184

185185
for _, rt := range initTest {
186186
t.Run(rt.name, func(t *testing.T) {
187-
_, _, err := runKubeadmInit(rt.args)
187+
_, _, _, err := runKubeadmInit(rt.args)
188188
if (err == nil) != rt.expected {
189189
t.Fatalf(dedent.Dedent(`
190190
CmdInitConfig test case %q failed with an error: %v
@@ -235,7 +235,7 @@ func TestCmdInitCertPhaseCSR(t *testing.T) {
235235
csrDir := testutil.SetupTempDir(t)
236236
cert := &certs.KubeadmCertKubeletClient
237237
kubeadmPath := getKubeadmPath()
238-
_, stderr, err := RunCmd(kubeadmPath,
238+
_, stderr, _, err := RunCmd(kubeadmPath,
239239
"init",
240240
"phase",
241241
"certs",
@@ -303,7 +303,7 @@ func TestCmdInitAPIPort(t *testing.T) {
303303

304304
for _, rt := range initTest {
305305
t.Run(rt.name, func(t *testing.T) {
306-
_, _, err := runKubeadmInit(rt.args)
306+
_, _, _, err := runKubeadmInit(rt.args)
307307
if (err == nil) != rt.expected {
308308
t.Fatalf(dedent.Dedent(`
309309
CmdInitAPIPort test case %q failed with an error: %v
@@ -321,3 +321,52 @@ func TestCmdInitAPIPort(t *testing.T) {
321321
})
322322
}
323323
}
324+
325+
// TestCmdInitFeatureGates test that feature gates won't make kubeadm panic.
326+
// When go panics it will exit with a 2 code. While we don't expect the init
327+
// calls to succeed in these tests, we ensure that the exit code of calling
328+
// kubeadm with different feature gates is not 2.
329+
func TestCmdInitFeatureGates(t *testing.T) {
330+
const PanicExitcode = 2
331+
332+
if *kubeadmCmdSkip {
333+
t.Log("kubeadm cmd tests being skipped")
334+
t.Skip()
335+
}
336+
337+
initTest := []struct {
338+
name string
339+
args string
340+
}{
341+
{
342+
name: "no feature gates passed",
343+
args: "",
344+
},
345+
{
346+
name: "feature gate CoreDNS=true",
347+
args: "--feature-gates=CoreDNS=true",
348+
},
349+
{
350+
name: "feature gate IPv6DualStack=true",
351+
args: "--feature-gates=IPv6DualStack=true",
352+
},
353+
}
354+
355+
for _, rt := range initTest {
356+
t.Run(rt.name, func(t *testing.T) {
357+
_, _, exitcode, err := runKubeadmInit(rt.args)
358+
if exitcode == PanicExitcode {
359+
t.Fatalf(dedent.Dedent(`
360+
CmdInitFeatureGates test case %q failed with an error: %v
361+
command 'kubeadm init %s'
362+
got exit code: %t (panic); unexpected
363+
`),
364+
rt.name,
365+
err,
366+
rt.args,
367+
PanicExitcode,
368+
)
369+
}
370+
})
371+
}
372+
}

cmd/kubeadm/test/cmd/join_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import "testing"
2121
// kubeadmReset executes "kubeadm reset" and restarts kubelet.
2222
func kubeadmReset() error {
2323
kubeadmPath := getKubeadmPath()
24-
_, _, err := RunCmd(kubeadmPath, "reset")
24+
_, _, _, err := RunCmd(kubeadmPath, "reset")
2525
return err
2626
}
2727

@@ -43,7 +43,7 @@ func TestCmdJoinConfig(t *testing.T) {
4343
kubeadmPath := getKubeadmPath()
4444
for _, rt := range initTest {
4545
t.Run(rt.name, func(t *testing.T) {
46-
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
46+
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
4747
if (actual == nil) != rt.expected {
4848
t.Errorf(
4949
"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) {
7676
kubeadmPath := getKubeadmPath()
7777
for _, rt := range initTest {
7878
t.Run(rt.name, func(t *testing.T) {
79-
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
79+
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
8080
if (actual == nil) != rt.expected {
8181
t.Errorf(
8282
"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) {
109109
kubeadmPath := getKubeadmPath()
110110
for _, rt := range initTest {
111111
t.Run(rt.name, func(t *testing.T) {
112-
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
112+
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
113113
if (actual == nil) != rt.expected {
114114
t.Errorf(
115115
"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) {
141141
kubeadmPath := getKubeadmPath()
142142
for _, rt := range initTest {
143143
t.Run(rt.name, func(t *testing.T) {
144-
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
144+
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
145145
if (actual == nil) != rt.expected {
146146
t.Errorf(
147147
"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) {
174174
kubeadmPath := getKubeadmPath()
175175
for _, rt := range initTest {
176176
t.Run(rt.name, func(t *testing.T) {
177-
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
177+
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
178178
if (actual == nil) != rt.expected {
179179
t.Errorf(
180180
"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) {
207207
kubeadmPath := getKubeadmPath()
208208
for _, rt := range initTest {
209209
t.Run(rt.name, func(t *testing.T) {
210-
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
210+
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
211211
if (actual == nil) != rt.expected {
212212
t.Errorf(
213213
"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) {
240240

241241
for _, rt := range initTest {
242242
t.Run(rt.name, func(t *testing.T) {
243-
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
243+
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
244244
if (actual == nil) != rt.expected {
245245
t.Errorf(
246246
"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) {
272272
kubeadmPath := getKubeadmPath()
273273
for _, rt := range initTest {
274274
t.Run(rt.name, func(t *testing.T) {
275-
_, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
275+
_, _, _, actual := RunCmd(kubeadmPath, "join", rt.args, "--ignore-preflight-errors=all")
276276
if (actual == nil) != rt.expected {
277277
t.Errorf(
278278
"failed CmdJoinArgsMixed running 'kubeadm join %s' with an error: %v\n\texpected: %t\n\t actual: %t",

cmd/kubeadm/test/cmd/token_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func TestCmdTokenGenerate(t *testing.T) {
4848
t.Log("kubeadm cmd tests being skipped")
4949
t.Skip()
5050
}
51-
stdout, _, err := RunCmd(kubeadmPath, "token", "generate")
51+
stdout, _, _, err := RunCmd(kubeadmPath, "token", "generate")
5252
if err != nil {
5353
t.Fatalf("'kubeadm token generate' exited uncleanly: %v", err)
5454
}
@@ -78,7 +78,7 @@ func TestCmdTokenGenerateTypoError(t *testing.T) {
7878
}
7979

8080
kubeadmPath := getKubeadmPath()
81-
_, _, err := RunCmd(kubeadmPath, "token", "genorate") // subtle typo
81+
_, _, _, err := RunCmd(kubeadmPath, "token", "genorate") // subtle typo
8282
if err == nil {
8383
t.Error("'kubeadm token genorate' (a deliberate typo) exited without an error when we expected non-zero exit status")
8484
}
@@ -101,7 +101,7 @@ func TestCmdTokenDelete(t *testing.T) {
101101
kubeadmPath := getKubeadmPath()
102102
for _, rt := range tests {
103103
t.Run(rt.name, func(t *testing.T) {
104-
_, _, actual := RunCmd(kubeadmPath, "token", "delete", rt.args)
104+
_, _, _, actual := RunCmd(kubeadmPath, "token", "delete", rt.args)
105105
if (actual == nil) != rt.expected {
106106
t.Errorf(
107107
"failed CmdTokenDelete running 'kubeadm token %s' with an error: %v\n\texpected: %t\n\t actual: %t",

cmd/kubeadm/test/cmd/util.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,24 @@ import (
2929
// Forked from test/e2e/framework because the e2e framework is quite bloated
3030
// for our purposes here, and modified to remove undesired logging.
3131

32-
func runCmdNoWrap(command string, args ...string) (string, string, error) {
32+
func runCmdNoWrap(command string, args ...string) (string, string, int, error) {
3333
var bout, berr bytes.Buffer
3434
cmd := exec.Command(command, args...)
3535
cmd.Stdout = &bout
3636
cmd.Stderr = &berr
3737
err := cmd.Run()
3838
stdout, stderr := bout.String(), berr.String()
39-
return stdout, stderr, err
39+
return stdout, stderr, cmd.ProcessState.ExitCode(), err
4040
}
4141

4242
// RunCmd is a utility function for kubeadm testing that executes a specified command
43-
func RunCmd(command string, args ...string) (string, string, error) {
44-
stdout, stderr, err := runCmdNoWrap(command, args...)
43+
func RunCmd(command string, args ...string) (string, string, int, error) {
44+
stdout, stderr, retcode, err := runCmdNoWrap(command, args...)
4545
if err != nil {
46-
return stdout, stderr, errors.Wrapf(err, "error running %s %v; \nstdout %q, \nstderr %q, \ngot error",
47-
command, args, stdout, stderr)
46+
return stdout, stderr, retcode, errors.Wrapf(err, "error running %s %v; \nretcode %d, \nstdout %q, \nstderr %q, \ngot error",
47+
command, args, retcode, stdout, stderr)
4848
}
49-
return stdout, stderr, nil
49+
return stdout, stderr, retcode, nil
5050
}
5151

5252
// RunSubCommand is a utility function for kubeadm testing that executes a Cobra sub command

cmd/kubeadm/test/cmd/version_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func TestCmdVersion(t *testing.T) {
5353
kubeadmPath := getKubeadmPath()
5454
for _, rt := range versionTest {
5555
t.Run(rt.name, func(t *testing.T) {
56-
stdout, _, actual := RunCmd(kubeadmPath, "version", rt.args)
56+
stdout, _, _, actual := RunCmd(kubeadmPath, "version", rt.args)
5757
if (actual == nil) != rt.expected {
5858
t.Errorf(
5959
"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) {
9696
kubeadmPath := getKubeadmPath()
9797
for _, rt := range versionTest {
9898
t.Run(rt.name, func(t *testing.T) {
99-
stdout, _, actual := RunCmd(kubeadmPath, "version", rt.args)
99+
stdout, _, _, actual := RunCmd(kubeadmPath, "version", rt.args)
100100
if (actual == nil) != rt.expected {
101101
t.Errorf(
102102
"failed CmdVersion running 'kubeadm version %s' with an error: %v\n\texpected: %t\n\t actual: %t",

0 commit comments

Comments
 (0)