blob: b9818b32f0d25dcb4302cdbfb0c067e5a4fdc974 [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 Bazanski216fe7b2021-05-21 18:36:16 +020017// This package launches a Metropolis cluster with two nodes and spawns in the
18// CTS container. Then it streams its output to the console. When the CTS has
19// finished it exits with the appropriate error code.
Lorenz Bruned0503c2020-07-28 17:21:25 +020020package main
21
22import (
23 "context"
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +020024 "errors"
Lorenz Bruned0503c2020-07-28 17:21:25 +020025 "io"
26 "log"
27 "os"
28 "os/signal"
29 "strings"
30 "syscall"
31 "time"
32
Lorenz Bruned0503c2020-07-28 17:21:25 +020033 corev1 "k8s.io/api/core/v1"
34 rbacv1 "k8s.io/api/rbac/v1"
35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020037 mlaunch "source.monogon.dev/metropolis/test/launch"
Lorenz Bruned0503c2020-07-28 17:21:25 +020038)
39
Serge Bazanski216fe7b2021-05-21 18:36:16 +020040// makeCTSPodSpec generates a spec for a standalone pod running the Kubernetes
41// CTS. It also sets the test configuration for the Kubernetes E2E test suite
42// to only run CTS tests and excludes known-broken ones.
Lorenz Bruned0503c2020-07-28 17:21:25 +020043func makeCTSPodSpec(name string, saName string) *corev1.Pod {
44 skipRegexes := []string{
Serge Bazanski216fe7b2021-05-21 18:36:16 +020045 // hostNetworking cannot be supported since we run different network
46 // stacks for the host and containers
Lorenz Bruned0503c2020-07-28 17:21:25 +020047 "should function for node-pod communication",
Serge Bazanski216fe7b2021-05-21 18:36:16 +020048 // gVisor misreports statfs() syscalls:
49 // https://github.com/google/gvisor/issues/3339
Lorenz Bruned0503c2020-07-28 17:21:25 +020050 `should support \((non-)?root,`,
51 "volume on tmpfs should have the correct mode",
52 "volume on default medium should have the correct mode",
Serge Bazanski216fe7b2021-05-21 18:36:16 +020053 // gVisor doesn't support the full Linux privilege machinery including
54 // SUID and NewPrivs:
55 // https://github.com/google/gvisor/issues/189#issuecomment-481064000
Lorenz Bruned0503c2020-07-28 17:21:25 +020056 "should run the container as unprivileged when false",
57 }
58 return &corev1.Pod{
59 ObjectMeta: metav1.ObjectMeta{
60 Name: name,
61 Labels: map[string]string{
62 "name": name,
63 },
64 },
65 Spec: corev1.PodSpec{
66 Containers: []corev1.Container{
67 {
68 Name: "cts",
Serge Bazanski77cb6c52020-12-19 00:09:22 +010069 Image: "bazel/metropolis/test/e2e/k8s_cts:k8s_cts_image",
Lorenz Bruned0503c2020-07-28 17:21:25 +020070 Args: []string{
71 "-cluster-ip-range=10.0.0.0/17",
72 "-dump-systemd-journal=false",
73 "-ginkgo.focus=\\[Conformance\\]",
74 "-ginkgo.skip=" + strings.Join(skipRegexes, "|"),
75 "-test.parallel=8",
76 },
77 ImagePullPolicy: corev1.PullNever,
78 },
79 },
Serge Bazanski216fe7b2021-05-21 18:36:16 +020080 // Tolerate all taints, otherwise the CTS likes to self-evict.
81 Tolerations: []corev1.Toleration{{
Lorenz Bruned0503c2020-07-28 17:21:25 +020082 Operator: "Exists",
83 }},
Serge Bazanski216fe7b2021-05-21 18:36:16 +020084 // Don't evict the CTS pod.
85 PriorityClassName: "system-cluster-critical",
Lorenz Bruned0503c2020-07-28 17:21:25 +020086 RestartPolicy: corev1.RestartPolicyNever,
87 ServiceAccountName: saName,
88 },
89 }
90}
91
92func main() {
93 sigs := make(chan os.Signal, 1)
94 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
95 ctx, cancel := context.WithCancel(context.Background())
96 go func() {
Lorenz Brun6df7c4f2020-12-21 15:02:00 +010097 sig := <-sigs
98 log.Printf("Got signal %s, aborting test", sig)
Lorenz Bruned0503c2020-07-28 17:21:25 +020099 cancel()
100 }()
101
Serge Bazanskif055a7f2021-04-13 16:22:33 +0200102 // TODO(q3k): bump up number of nodes after multi-node workflow gets reimplemented.
Tim Windelschmidt9f21f532024-05-07 15:14:20 +0200103 cl, err := mlaunch.LaunchCluster(ctx, mlaunch.ClusterOptions{NumNodes: 1})
Lorenz Bruned0503c2020-07-28 17:21:25 +0200104 if err != nil {
105 log.Fatalf("Failed to launch cluster: %v", err)
106 }
107 log.Println("Cluster initialized")
Lorenz Brun8f1254d2025-01-28 14:10:05 +0100108 clientSet, _, err := cl.GetKubeClientSet()
Lorenz Bruned0503c2020-07-28 17:21:25 +0200109 if err != nil {
110 log.Fatalf("Failed to get clientSet: %v", err)
111 }
112 log.Println("Credentials available")
113
114 saName := "cts"
115 ctsSA := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: saName}}
116 for {
117 if _, err := clientSet.CoreV1().ServiceAccounts("default").Create(ctx, ctsSA, metav1.CreateOptions{}); err != nil {
118 log.Printf("Failed to create ServiceAccount: %v", err)
119 time.Sleep(1 * time.Second)
120 continue
121 }
122 break
123 }
124 ctsRoleBinding := &rbacv1.ClusterRoleBinding{
125 ObjectMeta: metav1.ObjectMeta{
126 Name: saName,
127 },
128 Subjects: []rbacv1.Subject{
129 {
130 Namespace: "default",
131 Name: saName,
132 Kind: rbacv1.ServiceAccountKind,
133 },
134 },
135 RoleRef: rbacv1.RoleRef{
136 Kind: "ClusterRole",
137 Name: "cluster-admin",
138 },
139 }
140 podName := "cts"
141 if _, err := clientSet.RbacV1().ClusterRoleBindings().Create(ctx, ctsRoleBinding, metav1.CreateOptions{}); err != nil {
142 log.Fatalf("Failed to create ClusterRoleBinding: %v", err)
143 }
144 for {
145 if _, err := clientSet.CoreV1().Pods("default").Create(ctx, makeCTSPodSpec(podName, saName), metav1.CreateOptions{}); err != nil {
146 log.Printf("Failed to create Pod: %v", err)
147 time.Sleep(1 * time.Second)
148 continue
149 }
150 break
151 }
152 var logs io.ReadCloser
153 go func() {
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200154 // This loops the whole .Stream()/io.Copy process because the API
155 // sometimes returns streams that immediately return EOF
Lorenz Bruned0503c2020-07-28 17:21:25 +0200156 for {
157 logs, err = clientSet.CoreV1().Pods("default").GetLogs(podName, &corev1.PodLogOptions{Follow: true}).Stream(ctx)
158 if err == nil {
159 if _, err := io.Copy(os.Stdout, logs); err != nil {
160 log.Printf("Log pump error: %v", err)
161 }
162 logs.Close()
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200163 } else if errors.Is(err, ctx.Err()) {
Lorenz Brun6df7c4f2020-12-21 15:02:00 +0100164 return // Exit if the context has been cancelled
Lorenz Bruned0503c2020-07-28 17:21:25 +0200165 } else {
166 log.Printf("Pod logs not ready yet: %v", err)
167 }
168 time.Sleep(1 * time.Second)
169 }
170 }()
171 for {
172 time.Sleep(1 * time.Second)
173 pod, err := clientSet.CoreV1().Pods("default").Get(ctx, podName, metav1.GetOptions{})
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200174 if err != nil && errors.Is(err, ctx.Err()) {
Lorenz Brun6df7c4f2020-12-21 15:02:00 +0100175 return // Exit if the context has been cancelled
176 } else if err != nil {
Lorenz Bruned0503c2020-07-28 17:21:25 +0200177 log.Printf("Failed to get CTS pod: %v", err)
178 continue
179 }
180 if pod.Status.Phase == corev1.PodSucceeded {
181 return
182 }
183 if pod.Status.Phase == corev1.PodFailed {
184 log.Fatalf("CTS failed")
185 }
186 }
187}