blob: 728a8900cd41bc56140f231fb08da8e374c7f022 [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
17// This package launches a Smalltown Cluster with two nodes and spawns in the CTS container. Then it streams its output
18// 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 Bazanski77cb6c52020-12-19 00:09:22 +010035 common "git.monogon.dev/source/nexantic.git/metropolis/node"
36 "git.monogon.dev/source/nexantic.git/metropolis/test/e2e"
37 "git.monogon.dev/source/nexantic.git/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() {
91 <-sigs
92 cancel()
93 }()
94
95 debugClient, portMap, err := launch.LaunchCluster(ctx, launch.ClusterOptions{NumNodes: 2})
96 if err != nil {
97 log.Fatalf("Failed to launch cluster: %v", err)
98 }
99 log.Println("Cluster initialized")
100
101 clientSet, err := e2e.GetKubeClientSet(ctx, debugClient, portMap[common.KubernetesAPIPort])
102 if err != nil {
103 log.Fatalf("Failed to get clientSet: %v", err)
104 }
105 log.Println("Credentials available")
106
107 saName := "cts"
108 ctsSA := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: saName}}
109 for {
110 if _, err := clientSet.CoreV1().ServiceAccounts("default").Create(ctx, ctsSA, metav1.CreateOptions{}); err != nil {
111 log.Printf("Failed to create ServiceAccount: %v", err)
112 time.Sleep(1 * time.Second)
113 continue
114 }
115 break
116 }
117 ctsRoleBinding := &rbacv1.ClusterRoleBinding{
118 ObjectMeta: metav1.ObjectMeta{
119 Name: saName,
120 },
121 Subjects: []rbacv1.Subject{
122 {
123 Namespace: "default",
124 Name: saName,
125 Kind: rbacv1.ServiceAccountKind,
126 },
127 },
128 RoleRef: rbacv1.RoleRef{
129 Kind: "ClusterRole",
130 Name: "cluster-admin",
131 },
132 }
133 podName := "cts"
134 if _, err := clientSet.RbacV1().ClusterRoleBindings().Create(ctx, ctsRoleBinding, metav1.CreateOptions{}); err != nil {
135 log.Fatalf("Failed to create ClusterRoleBinding: %v", err)
136 }
137 for {
138 if _, err := clientSet.CoreV1().Pods("default").Create(ctx, makeCTSPodSpec(podName, saName), metav1.CreateOptions{}); err != nil {
139 log.Printf("Failed to create Pod: %v", err)
140 time.Sleep(1 * time.Second)
141 continue
142 }
143 break
144 }
145 var logs io.ReadCloser
146 go func() {
147 // This loops the whole .Stream()/io.Copy process because the API sometimes returns streams that immediately return EOF
148 for {
149 logs, err = clientSet.CoreV1().Pods("default").GetLogs(podName, &corev1.PodLogOptions{Follow: true}).Stream(ctx)
150 if err == nil {
151 if _, err := io.Copy(os.Stdout, logs); err != nil {
152 log.Printf("Log pump error: %v", err)
153 }
154 logs.Close()
155 } else {
156 log.Printf("Pod logs not ready yet: %v", err)
157 }
158 time.Sleep(1 * time.Second)
159 }
160 }()
161 for {
162 time.Sleep(1 * time.Second)
163 pod, err := clientSet.CoreV1().Pods("default").Get(ctx, podName, metav1.GetOptions{})
164 if err != nil {
165 log.Printf("Failed to get CTS pod: %v", err)
166 continue
167 }
168 if pod.Status.Phase == corev1.PodSucceeded {
169 return
170 }
171 if pod.Status.Phase == corev1.PodFailed {
172 log.Fatalf("CTS failed")
173 }
174 }
175}