|
| 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 | + |
| 125 | + |
| 126 | +### Starting Ginkgo |
| 127 | + |
| 128 | +A simple sequence diagram represents the run spec flow: |
| 129 | + |
| 130 | + |
| 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