Skip to content

Commit 81808c7

Browse files
authored
Merge pull request #5196 from knabben/node-test-suite
Node E2E test suite documentation
2 parents e37e32e + fb789ba commit 81808c7

File tree

3 files changed

+206
-0
lines changed

3 files changed

+206
-0
lines changed
37.1 KB
Loading
9.74 KB
Loading
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# Node End-To-End (e2e) Code Documentation
2+
3+
Kubernetes end-to-end (e2e) tests, at their core, work by compiling test code and creating an executable.
4+
The executable, in a way, is very much like any other application: it performs some tasks (it runs e2e tests) and to
5+
run it needs a Kubernetes cluster or a machine to run on.
6+
7+
Normal Kubernetes e2e tests require an entire cluster. The cluster is created during each CI run by prow and some
8+
other [test infra](https://github.com/kubernetes/test-infra) components.
9+
10+
Node e2e tests differ from regular e2e tests in the fact that node e2e tests aim only to test the node component
11+
of Kubernetes, the kubelet. In this scenario, the infrastructure needed is a kubelet, an API server
12+
(since it is the managing component of Kubernetes), and an ETCD server for the API server.
13+
14+
Because of this difference in infrastructure and environment needs, we had to create a test suite that would
15+
allow us to automate the management of the needed components during test runs and to allow
16+
contributors to work on developing tests.
17+
18+
The two core goals of the node test suite are to 1.) run e2e tests that verify the kubelet is working
19+
during CI test runs and 2.) to allow contributors to develop and iterate on tests outside of CI easily.
20+
21+
- *Goal #1* is implemented in the all too familiar way by compiling test code and using ginkgo to implement
22+
an entrypoint that will hand down control from the caller of the binary to ginkgo itself, which in turn manages
23+
what e2e tests to run and how.
24+
25+
- *Goal #2* is done by offering two entry points into the node test suite: a local and a remote runner.
26+
The local runner executes the node test suite in the machine in which it is called.
27+
The remote runner executes the node test suite in a remote machine (it currently only has an integration with GCP).
28+
29+
## Running node tests
30+
31+
As we already mentioned, node e2e tests can be executed locally or remotely (in a GCE VM).
32+
In either case, in order to run e2e tests we will make use of a couple of utility scripts and programs
33+
that exist in Kubernetes.
34+
35+
One of the tools we will use the most is the [Makefile](https://github.com/kubernetes/kubernetes/blob/master/build/root/Makefile)
36+
that is found at the root of the k/k repository.
37+
38+
In order to run node e2e tests, we will focus on the Makefile
39+
[test-e2e-node](https://github.com/kubernetes/kubernetes/blob/master/build/root/Makefile#L264-L270) target.
40+
41+
42+
### Local Runner
43+
44+
This is a compilation of notes on how to run E2E locally. Here will come the dissection of the
45+
steps that Make goes until Ginkgo runs the tests throughout the Kubernetes E2E framework.
46+
The first step to run the test in a local machine it should be:
47+
48+
```
49+
make test-e2e-node
50+
````
51+
52+
The test-e2e-node target can take a variety of arguments, to show them all you can print a help
53+
message by running:
54+
55+
```
56+
make test-e2e-node PRINT_HELP=y
57+
```
58+
59+
When running the make target without the helper option, the following shell script is called:
60+
*hack/make-rules/test-e2e-node.sh*, with the arguments from the target.
61+
62+
Some other prerequisites targets exist ginkgo, which builds the Ginkgo binary and generated_files,
63+
collecting all generated files sets into a single rule.
64+
(**gen_deepcopy**, **gen_defaulter**, **gen_conversion**, **gen_openapi**, etc.)
65+
66+
There are two kinds of runners for the E2E tests, the first analyzed here is the local runner,
67+
it uses the machine to build and run the tests, the other is the remote mode used in the CI.
68+
69+
The header of the shell script will set the internal variables and check if *REMOTE=true*
70+
is passed as an argument, otherwise, the make target will run the local runner with some
71+
translations and defaulting in the arguments not inserted.
72+
73+
The entrypoint for the local runner is the file: *test/e2e_node/runner/local/run_local.go*:
74+
75+
```bash
76+
if [ "${remote}" = true ] ; then
77+
...
78+
else
79+
# Test using the host the script was run on
80+
# Provided for backwards compatibility
81+
go run test/e2e_node/runner/local/run_local.go \
82+
--system-spec-name="${system_spec_name}" --extra-envs="${extra_envs}" \
83+
--ginkgo-flags="${ginkgoflags}" --test-flags="--container-runtime=${runtime} \
84+
--alsologtostderr --v 4 --report-dir=${artifacts} --node-name $(hostname) \
85+
${test_args}" --build-dependencies=true 2>&1 | tee -i "${artifacts}/build-log.txt"
86+
fi
87+
```
88+
89+
On *run_local.go*, The first step is to build the targets used to run the tests (*builder.BuildGo()*),
90+
including the entire suite via Ginkgo pre-compilation, this is made with the flag *--build-dependencies=true*:
91+
92+
```go
93+
var buildTargets = []string{
94+
"cmd/kubelet",
95+
"test/e2e_node/e2e_node.test",
96+
"vendor/github.com/onsi/ginkgo/ginkgo",
97+
"cluster/gce/gci/mounter",
98+
}
99+
100+
targets := strings.Join(buildTargets, "")
101+
exec.Command("make", "-C", KUBEROOT, fmt.Sprintf("WHAT=%s", targets)
102+
```
103+
104+
Since you will have the pre-compiled suite of tests in the *_output/local/go/bin/e2e_node.test*,
105+
and they can be invoked manually, the tool executes as a second step the runCommand,
106+
using Ginkgo to bootstrap the suite of tests and the binaries necessary to run it:
107+
108+
```bash
109+
$KUBEROOT/_output/local/go/bin/ginkgo -nodes=8 \
110+
-skip="\[Flaky\]|\[Slow\]|\[Serial\]"
111+
-untilItFails=false
112+
$KUBEROOT/_output/local/go/bin/e2e_node.test --
113+
--container-runtime=docker
114+
--alsologtostderr --v 4
115+
--report-dir=/tmp/_artifacts/200719T231746
116+
--node-name raspberrypi
117+
--kubelet-flags="--container-runtime=docker"
118+
--kubelet-flags="--network-plugin= --cni-bin-dir="
119+
```
120+
121+
Here is the flow we have from the Makefile target (test-e2e-node)
122+
until the actual run of the ginkgo script suite.
123+
124+
![Ginkgo start](images/ginkgo-start.png)
125+
126+
### Starting Ginkgo
127+
128+
A simple sequence diagram represents the run spec flow:
129+
130+
![Ginkgo sequence](images/ginkgo-flow.png)
131+
132+
The entry point for the system is *test/e2e_node/e2e_node_suite_test.go*, where it executes *init()*
133+
and *AddFileSource* registering providers for files that maybe be needed at runtime,
134+
after *TestMain* is called to register the flags and start Ginkgo setup.
135+
136+
The second phase is the **TestE2eNode**, which bootstrap the tests and the following modes can be used:
137+
138+
```go
139+
var runServicesMode = flag.Bool("run-services-mode", false, "If true, only run services (etcd, apiserver) in current process, and not run test.")
140+
var runKubeletMode = flag.Bool("run-kubelet-mode", false, "If true, only start kubelet, and not run test.")
141+
var systemValidateMode = flag.Bool("system-validate-mode", false, "If true, only run system validation in current process, and not run test.")
142+
```
143+
144+
If no mode is used the test suite is executed, first a new *Fail handler* is registered, and then the
145+
specs are started with a custom Junit format. Otherwise, the flags will control the startup of the
146+
cluster components.
147+
148+
```go
149+
if *runServicesMode { RunE2EService(); return }
150+
if *runKubeletMode { RunKubelet(); return }
151+
if *systemValidateMode { ValidateSpec(); return }
152+
```
153+
154+
### ginkgo.SynchronizedBeforeSuide
155+
156+
The first inner call is a Ginkgo helper function called before running the entire suite.
157+
158+
It starts with *validateSystem()*, this call the binary again with the *--system-validate-mode* flag,
159+
validating some system characteristics required to run (docker, OS-level, etc. the library is on k8s.io/system-validators).
160+
161+
In the sequence, if the option exist in the configuration, the images used in the tests are
162+
pre-pulled and must exist in the system, the list is on *NodePrePullImageList*.
163+
PrePullImages make sure the list is downloaded via *puller.Pull()* (can be docker or remote).
164+
165+
Finally, we have a crucial part that starts the required background services to run the test suite.
166+
167+
```go
168+
if *startServices {
169+
e2es = services.NewE2EServices(*stopServices)
170+
e2es.Start()
171+
}
172+
173+
waitForNodeReady()
174+
```
175+
176+
### Services
177+
178+
The files on *test/e2e_node/services* manages the e2e services in a separated process.
179+
180+
*E2EServices.Start()* brings Kubelet in the background if the framework is not running as NodeConformance,
181+
and *startInternalServices* calls the test binary with the flag *--run-services-mode*.
182+
183+
When running in service-mode it starts *test/e2e_node/services/internal_services.go:46:run*, which brings etcd,
184+
APIserver and starts a namespace controller.
185+
186+
```go
187+
klog.Info("Starting e2e services...")
188+
189+
err := es.startEtcd(t)
190+
if err != nil {
191+
return err
192+
}
193+
194+
err = es.startAPIServer(es.etcdStorage)
195+
if err != nil {
196+
return err
197+
}
198+
199+
err = es.startNamespaceController()
200+
if err != nil {
201+
return nil
202+
}
203+
204+
klog.Info("E2E services started.")
205+
return nil
206+
```

0 commit comments

Comments
 (0)