Skip to content
This repository was archived by the owner on Jul 30, 2021. It is now read-only.

Commit 06b25bd

Browse files
author
Yifan Gu
authored
Merge pull request #511 from pbx0/testing
start of e2e tests
2 parents eb07431 + 401f673 commit 06b25bd

File tree

4 files changed

+263
-1
lines changed

4 files changed

+263
-1
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ release: \
2020
check:
2121
@find . -name vendor -prune -o -name '*.go' -exec gofmt -s -d {} +
2222
@go vet $(shell go list ./... | grep -v '/vendor/')
23-
@go test -v $(shell go list ./... | grep -v '/vendor/')
23+
@go test -v $(shell go list ./... | grep -v '/vendor/\|/e2e')
2424

2525
install: _output/bin/$(LOCAL_OS)/bootkube
2626
cp $< $(GOPATH_BIN)

e2e/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## Bootkube E2E Testing
2+
3+
This is the beginnings of E2E testing for the bootkube repo using the standard go testing harness. To run the tests once you have a kubeconfig to a running cluster just execute:
4+
`go test -v --kubeconfig=<filepath> ./e2e/`
5+
6+
The number of nodes is required so that the setup can block on all nodes being registered.
7+
8+
## Roadmap
9+
10+
Implement whatever is needed to finish porting all functionality from pluton tests
11+
12+
## Requirements
13+
14+
Tests can't rely on network access to the cluster except via the kubernetes api. So no hitting nodes directly just because you can. This will maximize future portability with other setup tools.
15+
16+

e2e/main_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package e2e
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
"testing"
8+
"time"
9+
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/client-go/kubernetes"
12+
"k8s.io/client-go/pkg/api/v1"
13+
"k8s.io/client-go/tools/clientcmd"
14+
)
15+
16+
// global client for use by all tests
17+
var client kubernetes.Interface
18+
19+
// non-configurable for now
20+
const namespace = "bootkube-e2e-testing"
21+
22+
// TestMain handles setup before all tests
23+
func TestMain(m *testing.M) {
24+
var (
25+
kubeconfig = flag.String("kubeconfig", "../hack/quickstart/cluster/auth/kubeconfig", "absolute path to the kubeconfig file")
26+
)
27+
flag.Parse()
28+
29+
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
30+
if err != nil {
31+
fmt.Println(err)
32+
os.Exit(1)
33+
}
34+
// create the clientset
35+
client, err = kubernetes.NewForConfig(config)
36+
if err != nil {
37+
fmt.Println(err)
38+
os.Exit(1)
39+
}
40+
41+
if err := ready(client); err != nil {
42+
fmt.Println(err)
43+
os.Exit(1)
44+
}
45+
46+
// createNamespace
47+
if _, err := createNamespace(client, namespace); err != nil {
48+
fmt.Println(err)
49+
os.Exit(1)
50+
}
51+
52+
// run tests
53+
exitCode := m.Run()
54+
55+
if err := deleteNamespace(client, namespace); err != nil {
56+
fmt.Println(err)
57+
os.Exit(1)
58+
}
59+
60+
os.Exit(exitCode)
61+
}
62+
63+
func createNamespace(c kubernetes.Interface, name string) (*v1.Namespace, error) {
64+
namespace, err := c.CoreV1().Namespaces().Create(&v1.Namespace{
65+
ObjectMeta: metav1.ObjectMeta{
66+
Name: name,
67+
},
68+
})
69+
if err != nil {
70+
return nil, fmt.Errorf("failed to create namespace with name %v %v", name, namespace)
71+
}
72+
return namespace, nil
73+
}
74+
75+
func deleteNamespace(c kubernetes.Interface, name string) error {
76+
return c.CoreV1().Namespaces().Delete(name, nil)
77+
}
78+
79+
// Ready blocks until the cluster is considered available. The current
80+
// implementation checks that 1 schedulable node is ready.
81+
func ready(c kubernetes.Interface) error {
82+
f := func() error {
83+
list, err := c.CoreV1().Nodes().List(metav1.ListOptions{})
84+
if err != nil {
85+
return err
86+
}
87+
88+
if len(list.Items) < 1 {
89+
return fmt.Errorf("cluster is not ready, waiting for 1 or more worker nodes: %v", len(list.Items))
90+
}
91+
92+
// check for 1 or more ready nodes by ignoring nodes marked
93+
// unschedulable or containing taints
94+
var oneReady bool
95+
for _, node := range list.Items {
96+
if node.Spec.Unschedulable {
97+
continue
98+
}
99+
100+
if len(node.Spec.Taints) != 0 {
101+
continue
102+
}
103+
104+
for _, condition := range node.Status.Conditions {
105+
if condition.Type == v1.NodeReady {
106+
if condition.Status == v1.ConditionTrue {
107+
oneReady = true
108+
}
109+
break
110+
}
111+
}
112+
}
113+
if !oneReady {
114+
return fmt.Errorf("waiting for one worker node to be ready")
115+
}
116+
117+
return nil
118+
}
119+
120+
if err := retry(50, 10*time.Second, f); err != nil {
121+
return err
122+
}
123+
return nil
124+
}
125+
126+
func retry(attempts int, delay time.Duration, f func() error) error {
127+
var err error
128+
129+
for i := 0; i < attempts; i++ {
130+
err = f()
131+
if err == nil {
132+
break
133+
}
134+
135+
if i < attempts-1 {
136+
time.Sleep(delay)
137+
}
138+
}
139+
140+
return err
141+
}

e2e/smoke_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package e2e
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/client-go/pkg/api"
10+
"k8s.io/client-go/pkg/api/v1"
11+
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
12+
)
13+
14+
func TestSmoke(t *testing.T) {
15+
// 1. create nginx deployment
16+
di, _, err := api.Codecs.UniversalDecoder().Decode(nginxDep, nil, &v1beta1.Deployment{})
17+
if err != nil {
18+
t.Fatalf("unable to decode deployment manifest: %v", err)
19+
}
20+
d, ok := di.(*v1beta1.Deployment)
21+
if !ok {
22+
t.Fatalf("expected manifest to decode into *api.Service, got %T", di)
23+
}
24+
_, err = client.ExtensionsV1beta1().Deployments(namespace).Create(d)
25+
if err != nil {
26+
t.Fatal(err)
27+
}
28+
defer client.ExtensionsV1beta1().Deployments(namespace).Delete("nginx-deployment", &metav1.DeleteOptions{})
29+
30+
// 2. Get the nginx pod IP.
31+
var p *v1.Pod
32+
getPod := func() error {
33+
l, err := client.CoreV1().Pods(namespace).List(metav1.ListOptions{LabelSelector: "app=nginx"})
34+
if err != nil || len(l.Items) == 0 {
35+
return fmt.Errorf("pod not yet running: %v", err)
36+
}
37+
38+
// take the first pod
39+
p = &l.Items[0]
40+
41+
if p.Status.Phase != v1.PodRunning {
42+
return fmt.Errorf("pod not yet running: %v", p.Status.Phase)
43+
}
44+
return nil
45+
}
46+
if err := retry(10, time.Second*10, getPod); err != nil {
47+
t.Fatalf("timed out waiting for nginx pod: %v", err)
48+
}
49+
50+
// 3. Create a wget pod that hits the nginx pod IP.
51+
wgetPod.Spec.Containers[0].Command = []string{"wget", p.Status.PodIP}
52+
53+
_, err = client.CoreV1().Pods(namespace).Create(wgetPod)
54+
if err != nil {
55+
t.Fatal(err)
56+
}
57+
defer client.CoreV1().Pods(namespace).Delete(wgetPod.ObjectMeta.Name, &metav1.DeleteOptions{})
58+
59+
getPod = func() error {
60+
p, err = client.CoreV1().Pods(namespace).Get("wget-pod", metav1.GetOptions{})
61+
if err != nil {
62+
return fmt.Errorf("pod not yet running: %v", err)
63+
}
64+
if p.Status.Phase != v1.PodSucceeded {
65+
return fmt.Errorf("pod not yet running: %v", p.Status.Phase)
66+
}
67+
return nil
68+
}
69+
if err := retry(10, time.Second*10, getPod); err != nil {
70+
t.Fatalf(fmt.Sprintf("timed out waiting for wget pod to succeed: %v", err))
71+
}
72+
}
73+
74+
var nginxDep = []byte(`apiVersion: apps/v1beta1
75+
kind: Deployment
76+
metadata:
77+
name: nginx-deployment
78+
spec:
79+
replicas: 3
80+
template:
81+
metadata:
82+
labels:
83+
app: nginx
84+
spec:
85+
containers:
86+
- name: nginx
87+
image: nginx:1.8
88+
ports:
89+
- containerPort: 80`)
90+
91+
var wgetPod = &v1.Pod{
92+
ObjectMeta: metav1.ObjectMeta{
93+
Name: "wget-pod",
94+
Namespace: namespace,
95+
},
96+
Spec: v1.PodSpec{
97+
Containers: []v1.Container{
98+
{
99+
Name: "wget-container",
100+
Image: "busybox:1.26",
101+
},
102+
},
103+
RestartPolicy: v1.RestartPolicyNever,
104+
},
105+
}

0 commit comments

Comments
 (0)