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

Commit ffb8567

Browse files
author
Dongsu Park
committed
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 fcb9d37 commit ffb8567

File tree

3 files changed

+556
-0
lines changed

3 files changed

+556
-0
lines changed
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: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
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+
"io/ioutil"
25+
"os"
26+
"os/exec"
27+
"path/filepath"
28+
"strings"
29+
"testing"
30+
31+
"github.com/kinvolk/kube-spawn/pkg/utils"
32+
)
33+
34+
const (
35+
k8sStableVersion string = "v1.7.5"
36+
defaultKubeSpawnDir string = "/var/lib/kube-spawn"
37+
deploymentName string = "nginx-deployment"
38+
)
39+
40+
var (
41+
numNodes int = 2
42+
numDeploys int = 2
43+
44+
kubeSpawnDir string = defaultKubeSpawnDir
45+
kubeSpawnK8sPath string = filepath.Join(kubeSpawnDir, "k8s")
46+
kubeSpawnPath string
47+
kubeCtlPath string
48+
49+
machineCtlPath string
50+
goPath string
51+
cniPath string
52+
)
53+
54+
type Node struct {
55+
Name string
56+
IP string
57+
}
58+
59+
type Service struct {
60+
IP string
61+
Port string
62+
}
63+
64+
func checkRequirements(t *testing.T) {
65+
if os.Geteuid() != 0 {
66+
t.Fatal("smoke test requires root privileges")
67+
}
68+
}
69+
70+
func initPath(t *testing.T) {
71+
var err error
72+
73+
// go one dir upper, from "tests" to the top source directory
74+
if err := os.Chdir(".."); err != nil {
75+
t.Fatal(err)
76+
}
77+
78+
kubeSpawnPath = "./kube-spawn"
79+
if err := utils.CheckValidFile(kubeSpawnPath); err != nil {
80+
if kubeSpawnPath, err = exec.LookPath("kube-spawn"); err != nil {
81+
// fall back to an ordinary abspath to kube-spawn
82+
kubeSpawnPath = "/usr/bin/kube-spawn"
83+
}
84+
}
85+
86+
_ = os.MkdirAll(kubeSpawnK8sPath, os.FileMode(0755))
87+
88+
kubeCtlPath = filepath.Join(kubeSpawnK8sPath, "kubectl")
89+
if err := utils.CheckValidFile(kubeCtlPath); err != nil {
90+
if kubeCtlPath, err = exec.LookPath(kubeCtlPath); err != nil {
91+
// fall back to an ordinary abspath to kubectl
92+
kubeCtlPath = "/usr/bin/kubectl"
93+
}
94+
}
95+
96+
machineCtlPath, err = exec.LookPath("machinectl")
97+
if err != nil {
98+
// fall back to an ordinary abspath to machinectl
99+
machineCtlPath = "/usr/bin/machinectl"
100+
}
101+
102+
goPath = os.Getenv("GOPATH")
103+
if goPath == "" {
104+
t.Fatalf("GOPATH was not set")
105+
}
106+
cniPath = filepath.Join(goPath, "bin")
107+
os.Setenv("CNI_PATH", cniPath)
108+
}
109+
110+
func initNode(t *testing.T) {
111+
// If no coreos image exists, just download it
112+
if _, _, err := runCommand(fmt.Sprintf("%s show-image coreos", machineCtlPath)); err != nil {
113+
if stdout, stderr, err := runCommand(fmt.Sprintf("%s pull-raw --verify=no %s %s",
114+
machineCtlPath,
115+
"https://alpha.release.core-os.net/amd64-usr/current/coreos_developer_container.bin.bz2",
116+
"coreos",
117+
)); err != nil {
118+
t.Fatalf("error running machinectl pull-raw: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
119+
}
120+
}
121+
}
122+
123+
func getMachines(profileName string) ([]string, error) {
124+
var machNames []string
125+
126+
files, err := ioutil.ReadDir(filepath.Join(kubeSpawnDir, profileName))
127+
if err != nil {
128+
return nil, err
129+
}
130+
131+
for _, file := range files {
132+
if !strings.HasPrefix(file.Name(), "kubespawn") {
133+
continue
134+
}
135+
136+
machNames = append(machNames, file.Name())
137+
}
138+
139+
return machNames, nil
140+
}
141+
142+
func getListImages() ([]string, error) {
143+
var imageNames []string
144+
145+
stdout, stderr, err := runCommand(fmt.Sprintf("%s list-images --no-legend", machineCtlPath))
146+
if err != nil {
147+
return nil, fmt.Errorf("error running machinectl list-images: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
148+
}
149+
150+
s := bufio.NewScanner(strings.NewReader(strings.TrimSpace(stdout)))
151+
for s.Scan() {
152+
line := strings.Fields(s.Text())
153+
if len(line) <= 2 {
154+
continue
155+
}
156+
157+
// an example line:
158+
// kubespawn0 raw no 1.4G Wed 2017-10-25 02:15:19 CEST Wed 2017-10-25 02:15:19 CEST
159+
nodeName := strings.TrimSpace(line[0])
160+
if !strings.HasPrefix(nodeName, "kubespawn") {
161+
continue
162+
}
163+
164+
imageNames = append(imageNames, nodeName)
165+
}
166+
167+
return imageNames, nil
168+
}
169+
170+
func getRunningNodes() ([]Node, error) {
171+
var nodes []Node
172+
173+
stdout, stderr, err := runCommand(fmt.Sprintf("%s list --no-legend", machineCtlPath))
174+
if err != nil {
175+
return nil, fmt.Errorf("error running machinectl list: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
176+
}
177+
178+
s := bufio.NewScanner(strings.NewReader(strings.TrimSpace(stdout)))
179+
for s.Scan() {
180+
line := strings.Fields(s.Text())
181+
if len(line) <= 2 {
182+
continue
183+
}
184+
185+
// an example line from systemd v232 or newer:
186+
// kubespawn0 container systemd-nspawn coreos 1478.0.0 10.22.0.130...
187+
//
188+
// systemd v231 or older:
189+
// kubespawn0 container systemd-nspawn
190+
191+
var ipaddr string
192+
machineName := strings.TrimSpace(line[0])
193+
if !strings.HasPrefix(machineName, "kubespawn") {
194+
continue
195+
}
196+
197+
if len(line) >= 6 {
198+
ipaddr = strings.TrimSuffix(line[5], "...")
199+
} else {
200+
ipaddr, err = getIPAddressLegacy(machineName)
201+
if err != nil {
202+
return nil, err
203+
}
204+
}
205+
node := Node{
206+
Name: machineName,
207+
IP: ipaddr,
208+
}
209+
nodes = append(nodes, node)
210+
}
211+
212+
return nodes, nil
213+
}
214+
215+
func getIPAddressLegacy(mach string) (string, error) {
216+
// machinectl status kubespawn0 --no-pager | grep Address
217+
args := []string{
218+
"status",
219+
mach,
220+
"--no-pager",
221+
}
222+
223+
cmd := exec.Command("machinectl", args...)
224+
cmd.Stderr = os.Stderr
225+
cmd.Stdin = os.Stdin
226+
227+
b, err := cmd.Output()
228+
if err != nil {
229+
return "", err
230+
}
231+
232+
s := bufio.NewScanner(strings.NewReader(string(b)))
233+
for s.Scan() {
234+
// an example line is like this:
235+
//
236+
// Address: 10.22.0.4
237+
if strings.Contains(s.Text(), "Address:") {
238+
line := strings.TrimSpace(s.Text())
239+
fields := strings.Fields(line)
240+
if len(fields) <= 1 {
241+
continue
242+
}
243+
return fields[1], nil
244+
}
245+
}
246+
247+
return "", err
248+
}
249+
250+
func checkK8sNodes(t *testing.T) {
251+
nodeStates, err := waitForNReadyNodes(numNodes)
252+
if err != nil {
253+
t.Fatalf("error waiting on %d ready nodes, result %v: %v\n", numNodes, nodeStates, err)
254+
}
255+
}
256+
257+
func testCreateNodes(t *testing.T) {
258+
if stdout, stderr, err := runCommand(fmt.Sprintf("%s --kubernetes-version=%s create --nodes=%d",
259+
kubeSpawnPath, k8sStableVersion, numNodes),
260+
); err != nil {
261+
t.Fatalf("error running kube-spawn create: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
262+
}
263+
264+
machs, err := getMachines("default")
265+
if err != nil {
266+
t.Fatalf("error getting list of machines: %v\n", err)
267+
}
268+
if len(machs) != numNodes {
269+
t.Fatalf("got %d machines, expected %d machines.\n", len(machs), numNodes)
270+
}
271+
}
272+
273+
func testStartNodes(t *testing.T) {
274+
if out, err := runCommandCombinedOutput(fmt.Sprintf("%s start", kubeSpawnPath)); err != nil {
275+
t.Fatalf("error running kube-spawn start: %v\nstdout: %s\nstderr: %s", err, out)
276+
}
277+
278+
// set env variable KUBECONFIG to /var/lib/kube-spawn/default/kubeconfig
279+
if err := os.Setenv("KUBECONFIG", utils.GetValidKubeConfig()); err != nil {
280+
t.Fatalf("error running setenv: %v\n", err)
281+
}
282+
283+
images, err := getListImages()
284+
if err != nil {
285+
t.Fatalf("error getting list of images: %v\n", err)
286+
}
287+
if len(images) != numNodes {
288+
t.Fatalf("got %d images, expected %d images.\n", len(images), numNodes)
289+
}
290+
291+
nodes, err := getRunningNodes()
292+
if err != nil {
293+
t.Fatalf("error getting list of nodes: %v\n", err)
294+
}
295+
if len(nodes) != numNodes {
296+
t.Fatalf("got %d nodes, expected %d nodes.\n", len(nodes), numNodes)
297+
}
298+
299+
checkK8sNodes(t)
300+
}
301+
302+
func testApplyDeploy(t *testing.T) {
303+
if stdout, stderr, err := runCommand(fmt.Sprintf("%s apply -f %s",
304+
kubeCtlPath, "./tests/fixtures/nginx-deployment.yaml"),
305+
); err != nil {
306+
t.Fatalf("error creating deployment: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
307+
}
308+
309+
deploys, err := waitForNDeployments(numDeploys)
310+
if err != nil {
311+
t.Fatalf("error waiting on %d deployments, result %v: %v\n", numDeploys, deploys, err)
312+
}
313+
}
314+
315+
func testExposeDeploy(t *testing.T) {
316+
if stdout, stderr, err := runCommand(fmt.Sprintf("%s expose deployment/nginx-deployment",
317+
kubeCtlPath)); err != nil {
318+
t.Fatalf("error exposing deployment: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
319+
}
320+
}
321+
322+
func testServices(t *testing.T) {
323+
stdout, stderr, err := runCommand(fmt.Sprintf("%s get services --no-headers=true", kubeCtlPath))
324+
if err != nil {
325+
t.Fatalf("error getting services: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
326+
}
327+
328+
outStr := strings.TrimSpace(string(stdout))
329+
scanner := bufio.NewScanner(strings.NewReader(outStr))
330+
svcs := make(map[string]Service, 0)
331+
for scanner.Scan() {
332+
if len(strings.TrimSpace(scanner.Text())) == 0 {
333+
continue
334+
}
335+
336+
name := strings.TrimSpace(strings.Fields(scanner.Text())[0])
337+
clusterIP := strings.TrimSpace(strings.Fields(scanner.Text())[1])
338+
portSet := strings.Fields(scanner.Text())[3]
339+
port := strings.Split(portSet, "/")[0]
340+
341+
svcs[name] = Service{
342+
IP: clusterIP,
343+
Port: port,
344+
}
345+
}
346+
347+
if len(svcs) == 0 {
348+
t.Fatalf("cannot find any services\n")
349+
}
350+
351+
svc, ok := svcs["nginx-deployment"]
352+
if !ok {
353+
t.Fatalf("cannot find service nginx-deployment\n")
354+
}
355+
356+
testCheckConnectivity(t, svc)
357+
}
358+
359+
func testCheckConnectivity(t *testing.T, svc Service) {
360+
nodes, err := getRunningNodes()
361+
if err != nil {
362+
t.Fatalf("error getting running nodes: %v\n", err)
363+
}
364+
365+
// the cluster IP:Port should be reachable from any node
366+
stdout, stderr, err := runCommand(fmt.Sprintf("machinectl shell %s /usr/bin/curl %s:%s", nodes[0].Name, svc.IP, svc.Port))
367+
if err != nil {
368+
t.Fatalf("error checking for connectivity: %v\nstdout: %s\nstderr: %s", err, stdout, stderr)
369+
}
370+
}
371+
372+
func TestMainK8sStable(t *testing.T) {
373+
checkRequirements(t)
374+
initPath(t)
375+
initNode(t)
376+
377+
testCreateNodes(t)
378+
testStartNodes(t)
379+
testApplyDeploy(t)
380+
testExposeDeploy(t)
381+
testServices(t)
382+
}

0 commit comments

Comments
 (0)