Skip to content
This repository was archived by the owner on Jan 4, 2022. It is now read-only.

Commit 5a145d7

Browse files
Dongsu ParkDongsu Park
authored andcommitted
tests: initial implementation of smoke tests
A simple implementation of smoke tests based on Vagrant. It initializes a simple K8s cluster with 2 nodes, deploys an nginx pod, a service for the deployment, and checks if it's running. Fixes #56
1 parent a07ad6f commit 5a145d7

File tree

4 files changed

+388
-1
lines changed

4 files changed

+388
-1
lines changed

Gopkg.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: apps/v1beta1 # for versions before 1.6.0 use extensions/v1beta1
2+
kind: Deployment
3+
metadata:
4+
name: nginx-deployment
5+
spec:
6+
replicas: 2
7+
template:
8+
metadata:
9+
labels:
10+
app: nginx
11+
spec:
12+
containers:
13+
- name: nginx
14+
image: nginx:1.7.9
15+
ports:
16+
- containerPort: 80

tests/init_test.go

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/*
2+
Copyright 2017 Kinvolk GmbH
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// +build integration
18+
19+
package tests
20+
21+
import (
22+
"bufio"
23+
"fmt"
24+
"os"
25+
"os/exec"
26+
"path/filepath"
27+
"strings"
28+
"testing"
29+
30+
"github.com/kinvolk/kube-spawn/pkg/utils"
31+
)
32+
33+
const (
34+
k8sStableVersion string = "1.7.5"
35+
defaultKubeSpawnDir string = "/var/lib/kube-spawn"
36+
deploymentName string = "nginx-deployment"
37+
)
38+
39+
var (
40+
numNodes int = 2
41+
numDeploys int = 2
42+
43+
kubeSpawnDir string = defaultKubeSpawnDir
44+
kubeSpawnK8sPath string = filepath.Join(kubeSpawnDir, "k8s")
45+
kubeSpawnPath string
46+
kubeCtlPath string
47+
48+
machineCtlPath string
49+
)
50+
51+
func checkRequirements(t *testing.T) {
52+
if os.Geteuid() != 0 {
53+
t.Fatal("smoke test requires root privileges")
54+
}
55+
}
56+
57+
func initPath(t *testing.T) {
58+
var err error
59+
60+
// go one dir upper, from "tests" to the top source directory
61+
if err := os.Chdir(".."); err != nil {
62+
t.Fatal(err)
63+
}
64+
65+
kubeSpawnPath = "./kube-spawn"
66+
if err := utils.CheckValidFile(kubeSpawnPath); err != nil {
67+
if kubeSpawnPath, err = exec.LookPath("kube-spawn"); err != nil {
68+
// fall back to an ordinary abspath to kube-spawn
69+
kubeSpawnPath = "/usr/bin/kube-spawn"
70+
}
71+
}
72+
73+
_ = os.MkdirAll(kubeSpawnK8sPath, os.FileMode(0755))
74+
75+
kubeCtlPath = filepath.Join(kubeSpawnK8sPath, "kubectl")
76+
if err := utils.CheckValidFile(kubeCtlPath); err != nil {
77+
if kubeCtlPath, err = exec.LookPath(kubeCtlPath); err != nil {
78+
// fall back to an ordinary abspath to kubectl
79+
kubeCtlPath = "/usr/bin/kubectl"
80+
}
81+
}
82+
83+
machineCtlPath, err = exec.LookPath("machinectl")
84+
if err != nil {
85+
// fall back to an ordinary abspath to machinectl
86+
machineCtlPath = "/usr/bin/machinectl"
87+
}
88+
}
89+
90+
func initNode(t *testing.T) {
91+
// If no coreos image exists, just download it
92+
if _, _, err := runCommand(fmt.Sprintf("%s show-image coreos", machineCtlPath)); err != nil {
93+
if stdout, stderr, err := runCommand(fmt.Sprintf("%s pull-raw --verify=no %s %s",
94+
machineCtlPath,
95+
"https://alpha.release.core-os.net/amd64-usr/current/coreos_developer_container.bin.bz2",
96+
"coreos",
97+
)); err != nil {
98+
t.Fatalf("error running machinectl pull-raw: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
99+
}
100+
}
101+
}
102+
103+
func getRunningNodes() ([]string, error) {
104+
var nodeNames []string
105+
106+
stdout, stderr, err := runCommand(fmt.Sprintf("%s list --no-legend", machineCtlPath))
107+
if err != nil {
108+
return nil, fmt.Errorf("error running machinectl list: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
109+
}
110+
111+
s := bufio.NewScanner(strings.NewReader(strings.TrimSpace(stdout)))
112+
for s.Scan() {
113+
line := strings.Fields(s.Text())
114+
if len(line) <= 2 {
115+
continue
116+
}
117+
118+
// an example line:
119+
// kubespawn0 container systemd-nspawn coreos 1478.0.0 10.22.0.130...
120+
nodeName := strings.TrimSpace(line[0])
121+
if !strings.HasPrefix(nodeName, "kubespawn") {
122+
continue
123+
}
124+
125+
nodeNames = append(nodeNames, nodeName)
126+
}
127+
128+
return nodeNames, nil
129+
}
130+
131+
func checkK8sNodes(t *testing.T) {
132+
nodeStates, err := waitForNReadyNodes(numNodes)
133+
if err != nil {
134+
t.Fatalf("error waiting on %d ready nodes, result %v: %v\n", numNodes, nodeStates, err)
135+
}
136+
}
137+
138+
func testSetupK8sStable(t *testing.T) {
139+
if stdout, stderr, err := runCommand(fmt.Sprintf("%s --kubernetes-version=%s setup --nodes=%d",
140+
kubeSpawnPath, k8sStableVersion, numNodes),
141+
); err != nil {
142+
t.Fatalf("error running kube-spawn setup: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
143+
}
144+
145+
nodes, err := getRunningNodes()
146+
if err != nil {
147+
t.Fatalf("error getting list of running nodes: %v\n", err)
148+
}
149+
if len(nodes) != numNodes {
150+
t.Fatalf("got %d nodes, expected %d nodes.\n", len(nodes), numNodes)
151+
}
152+
}
153+
154+
func testInitK8sStable(t *testing.T) {
155+
if stdout, stderr, err := runCommand(fmt.Sprintf("%s --kubernetes-version=%s init",
156+
kubeSpawnPath, k8sStableVersion),
157+
); err != nil {
158+
t.Fatalf("error running kube-spawn init: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
159+
}
160+
161+
// set env variable KUBECONFIG to /var/lib/kube-spawn/default/kubeconfig
162+
if err := os.Setenv("KUBECONFIG", utils.GetValidKubeConfig()); err != nil {
163+
t.Fatalf("error running setenv: %v\n", err)
164+
}
165+
checkK8sNodes(t)
166+
}
167+
168+
func testCreateDeploy(t *testing.T) {
169+
if stdout, stderr, err := runCommand(fmt.Sprintf("%s create -f %s",
170+
kubeCtlPath, "./tests/fixtures/nginx-deployment.yaml"),
171+
); err != nil {
172+
t.Fatalf("error creating deployment: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
173+
}
174+
175+
deploys, err := waitForNDeployments(numDeploys)
176+
if err != nil {
177+
t.Fatalf("error waiting on %d deployments, result %v: %v\n", numDeploys, deploys, err)
178+
}
179+
}
180+
181+
func testServices(t *testing.T) {
182+
stdout, stderr, err := runCommand(fmt.Sprintf("%s get services --no-headers=true", kubeCtlPath))
183+
if err != nil {
184+
t.Fatalf("error getting services: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
185+
}
186+
187+
outStr := strings.TrimSpace(string(stdout))
188+
scanner := bufio.NewScanner(strings.NewReader(outStr))
189+
var svcNames []string
190+
for scanner.Scan() {
191+
if len(strings.TrimSpace(scanner.Text())) == 0 {
192+
continue
193+
}
194+
svcNames = append(svcNames, strings.Fields(scanner.Text())[0])
195+
}
196+
197+
if len(svcNames) == 0 {
198+
t.Fatalf("cannot find any services\n")
199+
}
200+
201+
if !existInSlice("kubernetes", svcNames) {
202+
t.Fatalf("cannot find a service\n")
203+
}
204+
}
205+
206+
func testDeleteDeploy(t *testing.T) {
207+
stdout, stderr, err := runCommand(fmt.Sprintf("%s delete deployment %s", kubeCtlPath, deploymentName))
208+
if err != nil {
209+
t.Fatalf("error deleting deployments %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
210+
}
211+
}
212+
213+
func TestMainK8sStable(t *testing.T) {
214+
checkRequirements(t)
215+
initPath(t)
216+
initNode(t)
217+
218+
testSetupK8sStable(t)
219+
testInitK8sStable(t)
220+
testCreateDeploy(t)
221+
testServices(t)
222+
}

tests/utils.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
Copyright 2017 Kinvolk GmbH
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// +build integration
18+
19+
package tests
20+
21+
import (
22+
"bufio"
23+
"bytes"
24+
"fmt"
25+
"log"
26+
"os/exec"
27+
"strconv"
28+
"strings"
29+
"time"
30+
)
31+
32+
func runCommand(command string) (string, string, error) {
33+
log.Printf(command)
34+
35+
var stdoutBytes, stderrBytes bytes.Buffer
36+
args := strings.Split(command, " ")
37+
cmd := exec.Command(args[0], args[1:]...)
38+
cmd.Stdout = &stdoutBytes
39+
cmd.Stderr = &stderrBytes
40+
err := cmd.Run()
41+
return stdoutBytes.String(), stderrBytes.String(), err
42+
}
43+
44+
func existInSlice(key string, inSlice []string) bool {
45+
for _, n := range inSlice {
46+
if n == key {
47+
return true
48+
}
49+
}
50+
return false
51+
}
52+
53+
func waitForNReadyNodes(expectedNodes int) (map[string]string, error) {
54+
nodeStates := make(map[string]string, 0)
55+
timeout := 180 * time.Second
56+
alarm := time.After(timeout)
57+
58+
getReadyNodes := func(nodeStates map[string]string) int {
59+
nReadyNodes := 0
60+
61+
for _, s := range nodeStates {
62+
if s != "Ready" {
63+
continue
64+
}
65+
nReadyNodes += 1
66+
}
67+
68+
return nReadyNodes
69+
}
70+
71+
ticker := time.Tick(500 * time.Millisecond)
72+
loop:
73+
for {
74+
select {
75+
case <-alarm:
76+
return nodeStates, fmt.Errorf("failed to find %d ready nodes within %v", expectedNodes, timeout)
77+
case <-ticker:
78+
stdout, _, err := runCommand(fmt.Sprintf("%s get nodes --no-headers=true", kubeCtlPath))
79+
if err != nil {
80+
continue
81+
}
82+
83+
outStr := strings.TrimSpace(stdout)
84+
scanner := bufio.NewScanner(strings.NewReader(outStr))
85+
nodeStates := make(map[string]string, 0)
86+
for scanner.Scan() {
87+
if len(strings.TrimSpace(scanner.Text())) == 0 {
88+
continue
89+
}
90+
name := strings.Fields(scanner.Text())[0]
91+
state := strings.Fields(scanner.Text())[1]
92+
nodeStates[name] = state
93+
}
94+
95+
if getReadyNodes(nodeStates) != expectedNodes {
96+
continue
97+
}
98+
99+
break loop
100+
}
101+
}
102+
103+
return nodeStates, nil
104+
}
105+
106+
func waitForNDeployments(expectedDeps int) (map[string]string, error) {
107+
deploys := make(map[string]string, 0)
108+
timeout := 10 * time.Second
109+
alarm := time.After(timeout)
110+
111+
ticker := time.Tick(250 * time.Millisecond)
112+
loop:
113+
for {
114+
select {
115+
case <-alarm:
116+
return deploys, fmt.Errorf("failed to find %d deployments within %v", expectedDeps, timeout)
117+
case <-ticker:
118+
stdout, _, err := runCommand(fmt.Sprintf("%s get deployments --no-headers=true", kubeCtlPath))
119+
if err != nil {
120+
continue
121+
}
122+
123+
outStr := strings.TrimSpace(stdout)
124+
scanner := bufio.NewScanner(strings.NewReader(outStr))
125+
deploys := make(map[string]string, 0)
126+
for scanner.Scan() {
127+
if len(strings.TrimSpace(scanner.Text())) == 0 {
128+
continue
129+
}
130+
// example of output:
131+
//
132+
// NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
133+
// nginx-deployment 2 2 2 2 3m
134+
depName := strings.Fields(scanner.Text())[0]
135+
depAvailable := strings.Fields(scanner.Text())[4]
136+
deploys[depName] = depAvailable
137+
}
138+
139+
actualNodes, _ := strconv.Atoi(deploys[deploymentName])
140+
if actualNodes != expectedDeps {
141+
continue
142+
}
143+
144+
break loop
145+
}
146+
}
147+
148+
return deploys, nil
149+
}

0 commit comments

Comments
 (0)