blob: e419b2f34fa55859f9a6a17a57389781a3134afd [file] [log] [blame]
Lorenz Bruned0503c2020-07-28 17:21:25 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
Serge Bazanski662b5b32020-12-21 13:49:00 +010017// This package launches a Metropolis cluster with two nodes and spawns in the CTS container. Then it streams its output
Lorenz Bruned0503c2020-07-28 17:21:25 +020018// to the console. When the CTS has finished it exits with the appropriate error code.
19package main
20
21import (
22 "context"
23 "io"
24 "log"
25 "os"
26 "os/signal"
27 "strings"
28 "syscall"
29 "time"
30
Lorenz Bruned0503c2020-07-28 17:21:25 +020031 corev1 "k8s.io/api/core/v1"
32 rbacv1 "k8s.io/api/rbac/v1"
33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34
Serge Bazanski31370b02021-01-07 16:31:14 +010035 common "source.monogon.dev/metropolis/node"
36 "source.monogon.dev/metropolis/test/e2e"
37 "source.monogon.dev/metropolis/test/launch"
Lorenz Bruned0503c2020-07-28 17:21:25 +020038)
39
40// makeCTSPodSpec generates a spec for a standalone pod running the Kubernetes CTS. It also sets the test configuration
41// for the Kubernetes E2E test suite to only run CTS tests and excludes known-broken ones.
42func makeCTSPodSpec(name string, saName string) *corev1.Pod {
43 skipRegexes := []string{
44 // hostNetworking cannot be supported since we run different network stacks for the host and containers
45 "should function for node-pod communication",
46 // gVisor misreports statfs() syscalls: https://github.com/google/gvisor/issues/3339
47 `should support \((non-)?root,`,
48 "volume on tmpfs should have the correct mode",
49 "volume on default medium should have the correct mode",
50 // gVisor doesn't support the full Linux privilege machinery including SUID and NewPrivs
51 // https://github.com/google/gvisor/issues/189#issuecomment-481064000
52 "should run the container as unprivileged when false",
53 }
54 return &corev1.Pod{
55 ObjectMeta: metav1.ObjectMeta{
56 Name: name,
57 Labels: map[string]string{
58 "name": name,
59 },
60 },
61 Spec: corev1.PodSpec{
62 Containers: []corev1.Container{
63 {
64 Name: "cts",
Serge Bazanski77cb6c52020-12-19 00:09:22 +010065 Image: "bazel/metropolis/test/e2e/k8s_cts:k8s_cts_image",
Lorenz Bruned0503c2020-07-28 17:21:25 +020066 Args: []string{
67 "-cluster-ip-range=10.0.0.0/17",
68 "-dump-systemd-journal=false",
69 "-ginkgo.focus=\\[Conformance\\]",
70 "-ginkgo.skip=" + strings.Join(skipRegexes, "|"),
71 "-test.parallel=8",
72 },
73 ImagePullPolicy: corev1.PullNever,
74 },
75 },
76 Tolerations: []corev1.Toleration{{ // Tolerate all taints, otherwise the CTS likes to self-evict
77 Operator: "Exists",
78 }},
79 PriorityClassName: "system-cluster-critical", // Don't evict the CTS pod
80 RestartPolicy: corev1.RestartPolicyNever,
81 ServiceAccountName: saName,
82 },
83 }
84}
85
86func main() {
87 sigs := make(chan os.Signal, 1)
88 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
89 ctx, cancel := context.WithCancel(context.Background())
90 go func() {
Lorenz Brun6df7c4f2020-12-21 15:02:00 +010091 sig := <-sigs
92 log.Printf("Got signal %s, aborting test", sig)
Lorenz Bruned0503c2020-07-28 17:21:25 +020093 cancel()
94 }()
95
Serge Bazanskif055a7f2021-04-13 16:22:33 +020096 // TODO(q3k): bump up number of nodes after multi-node workflow gets reimplemented.
97 debugClient, portMap, err := launch.LaunchCluster(ctx, launch.ClusterOptions{NumNodes: 1})
Lorenz Bruned0503c2020-07-28 17:21:25 +020098 if err != nil {
99 log.Fatalf("Failed to launch cluster: %v", err)
100 }
101 log.Println("Cluster initialized")
Lorenz Bruned0503c2020-07-28 17:21:25 +0200102 clientSet, err := e2e.GetKubeClientSet(ctx, debugClient, portMap[common.KubernetesAPIPort])
103 if err != nil {
104 log.Fatalf("Failed to get clientSet: %v", err)
105 }
106 log.Println("Credentials available")
107
108 saName := "cts"
109 ctsSA := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: saName}}
110 for {
111 if _, err := clientSet.CoreV1().ServiceAccounts("default").Create(ctx, ctsSA, metav1.CreateOptions{}); err != nil {
112 log.Printf("Failed to create ServiceAccount: %v", err)
113 time.Sleep(1 * time.Second)
114 continue
115 }
116 break
117 }
118 ctsRoleBinding := &rbacv1.ClusterRoleBinding{
119 ObjectMeta: metav1.ObjectMeta{
120 Name: saName,
121 },
122 Subjects: []rbacv1.Subject{
123 {
124 Namespace: "default",
125 Name: saName,
126 Kind: rbacv1.ServiceAccountKind,
127 },
128 },
129 RoleRef: rbacv1.RoleRef{
130 Kind: "ClusterRole",
131 Name: "cluster-admin",
132 },
133 }
134 podName := "cts"
135 if _, err := clientSet.RbacV1().ClusterRoleBindings().Create(ctx, ctsRoleBinding, metav1.CreateOptions{}); err != nil {
136 log.Fatalf("Failed to create ClusterRoleBinding: %v", err)
137 }
138 for {
139 if _, err := clientSet.CoreV1().Pods("default").Create(ctx, makeCTSPodSpec(podName, saName), metav1.CreateOptions{}); err != nil {
140 log.Printf("Failed to create Pod: %v", err)
141 time.Sleep(1 * time.Second)
142 continue
143 }
144 break
145 }
146 var logs io.ReadCloser
147 go func() {
148 // This loops the whole .Stream()/io.Copy process because the API sometimes returns streams that immediately return EOF
149 for {
150 logs, err = clientSet.CoreV1().Pods("default").GetLogs(podName, &corev1.PodLogOptions{Follow: true}).Stream(ctx)
151 if err == nil {
152 if _, err := io.Copy(os.Stdout, logs); err != nil {
153 log.Printf("Log pump error: %v", err)
154 }
155 logs.Close()
Lorenz Brun6df7c4f2020-12-21 15:02:00 +0100156 } else if err == ctx.Err() {
157 return // Exit if the context has been cancelled
Lorenz Bruned0503c2020-07-28 17:21:25 +0200158 } else {
159 log.Printf("Pod logs not ready yet: %v", err)
160 }
161 time.Sleep(1 * time.Second)
162 }
163 }()
164 for {
165 time.Sleep(1 * time.Second)
166 pod, err := clientSet.CoreV1().Pods("default").Get(ctx, podName, metav1.GetOptions{})
Lorenz Brun6df7c4f2020-12-21 15:02:00 +0100167 if err != nil && err == ctx.Err() {
168 return // Exit if the context has been cancelled
169 } else if err != nil {
Lorenz Bruned0503c2020-07-28 17:21:25 +0200170 log.Printf("Failed to get CTS pod: %v", err)
171 continue
172 }
173 if pod.Status.Phase == corev1.PodSucceeded {
174 return
175 }
176 if pod.Status.Phase == corev1.PodFailed {
177 log.Fatalf("CTS failed")
178 }
179 }
180}