blob: 4b86b64030d386ce8904f8f9f5e8069f5d326408 [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"
24 "io"
25 "log"
26 "os"
27 "os/signal"
28 "strings"
29 "syscall"
30 "time"
31
Lorenz Bruned0503c2020-07-28 17:21:25 +020032 corev1 "k8s.io/api/core/v1"
33 rbacv1 "k8s.io/api/rbac/v1"
34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35
Serge Bazanski66e58952021-10-05 17:06:56 +020036 "source.monogon.dev/metropolis/test/launch/cluster"
Lorenz Bruned0503c2020-07-28 17:21:25 +020037)
38
Serge Bazanski216fe7b2021-05-21 18:36:16 +020039// makeCTSPodSpec generates a spec for a standalone pod running the Kubernetes
40// CTS. It also sets the test configuration for the Kubernetes E2E test suite
41// to only run CTS tests and excludes known-broken ones.
Lorenz Bruned0503c2020-07-28 17:21:25 +020042func makeCTSPodSpec(name string, saName string) *corev1.Pod {
43 skipRegexes := []string{
Serge Bazanski216fe7b2021-05-21 18:36:16 +020044 // hostNetworking cannot be supported since we run different network
45 // stacks for the host and containers
Lorenz Bruned0503c2020-07-28 17:21:25 +020046 "should function for node-pod communication",
Serge Bazanski216fe7b2021-05-21 18:36:16 +020047 // gVisor misreports statfs() syscalls:
48 // https://github.com/google/gvisor/issues/3339
Lorenz Bruned0503c2020-07-28 17:21:25 +020049 `should support \((non-)?root,`,
50 "volume on tmpfs should have the correct mode",
51 "volume on default medium should have the correct mode",
Serge Bazanski216fe7b2021-05-21 18:36:16 +020052 // gVisor doesn't support the full Linux privilege machinery including
53 // SUID and NewPrivs:
54 // https://github.com/google/gvisor/issues/189#issuecomment-481064000
Lorenz Bruned0503c2020-07-28 17:21:25 +020055 "should run the container as unprivileged when false",
56 }
57 return &corev1.Pod{
58 ObjectMeta: metav1.ObjectMeta{
59 Name: name,
60 Labels: map[string]string{
61 "name": name,
62 },
63 },
64 Spec: corev1.PodSpec{
65 Containers: []corev1.Container{
66 {
67 Name: "cts",
Serge Bazanski77cb6c52020-12-19 00:09:22 +010068 Image: "bazel/metropolis/test/e2e/k8s_cts:k8s_cts_image",
Lorenz Bruned0503c2020-07-28 17:21:25 +020069 Args: []string{
70 "-cluster-ip-range=10.0.0.0/17",
71 "-dump-systemd-journal=false",
72 "-ginkgo.focus=\\[Conformance\\]",
73 "-ginkgo.skip=" + strings.Join(skipRegexes, "|"),
74 "-test.parallel=8",
75 },
76 ImagePullPolicy: corev1.PullNever,
77 },
78 },
Serge Bazanski216fe7b2021-05-21 18:36:16 +020079 // Tolerate all taints, otherwise the CTS likes to self-evict.
80 Tolerations: []corev1.Toleration{{
Lorenz Bruned0503c2020-07-28 17:21:25 +020081 Operator: "Exists",
82 }},
Serge Bazanski216fe7b2021-05-21 18:36:16 +020083 // Don't evict the CTS pod.
84 PriorityClassName: "system-cluster-critical",
Lorenz Bruned0503c2020-07-28 17:21:25 +020085 RestartPolicy: corev1.RestartPolicyNever,
86 ServiceAccountName: saName,
87 },
88 }
89}
90
91func main() {
92 sigs := make(chan os.Signal, 1)
93 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
94 ctx, cancel := context.WithCancel(context.Background())
95 go func() {
Lorenz Brun6df7c4f2020-12-21 15:02:00 +010096 sig := <-sigs
97 log.Printf("Got signal %s, aborting test", sig)
Lorenz Bruned0503c2020-07-28 17:21:25 +020098 cancel()
99 }()
100
Serge Bazanskif055a7f2021-04-13 16:22:33 +0200101 // TODO(q3k): bump up number of nodes after multi-node workflow gets reimplemented.
Serge Bazanski66e58952021-10-05 17:06:56 +0200102 cl, err := cluster.LaunchCluster(ctx, cluster.ClusterOptions{NumNodes: 1})
Lorenz Bruned0503c2020-07-28 17:21:25 +0200103 if err != nil {
104 log.Fatalf("Failed to launch cluster: %v", err)
105 }
106 log.Println("Cluster initialized")
Serge Bazanskia0bc6d32023-06-28 18:57:40 +0200107 clientSet, err := cl.GetKubeClientSet()
Lorenz Bruned0503c2020-07-28 17:21:25 +0200108 if err != nil {
109 log.Fatalf("Failed to get clientSet: %v", err)
110 }
111 log.Println("Credentials available")
112
113 saName := "cts"
114 ctsSA := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: saName}}
115 for {
116 if _, err := clientSet.CoreV1().ServiceAccounts("default").Create(ctx, ctsSA, metav1.CreateOptions{}); err != nil {
117 log.Printf("Failed to create ServiceAccount: %v", err)
118 time.Sleep(1 * time.Second)
119 continue
120 }
121 break
122 }
123 ctsRoleBinding := &rbacv1.ClusterRoleBinding{
124 ObjectMeta: metav1.ObjectMeta{
125 Name: saName,
126 },
127 Subjects: []rbacv1.Subject{
128 {
129 Namespace: "default",
130 Name: saName,
131 Kind: rbacv1.ServiceAccountKind,
132 },
133 },
134 RoleRef: rbacv1.RoleRef{
135 Kind: "ClusterRole",
136 Name: "cluster-admin",
137 },
138 }
139 podName := "cts"
140 if _, err := clientSet.RbacV1().ClusterRoleBindings().Create(ctx, ctsRoleBinding, metav1.CreateOptions{}); err != nil {
141 log.Fatalf("Failed to create ClusterRoleBinding: %v", err)
142 }
143 for {
144 if _, err := clientSet.CoreV1().Pods("default").Create(ctx, makeCTSPodSpec(podName, saName), metav1.CreateOptions{}); err != nil {
145 log.Printf("Failed to create Pod: %v", err)
146 time.Sleep(1 * time.Second)
147 continue
148 }
149 break
150 }
151 var logs io.ReadCloser
152 go func() {
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200153 // This loops the whole .Stream()/io.Copy process because the API
154 // sometimes returns streams that immediately return EOF
Lorenz Bruned0503c2020-07-28 17:21:25 +0200155 for {
156 logs, err = clientSet.CoreV1().Pods("default").GetLogs(podName, &corev1.PodLogOptions{Follow: true}).Stream(ctx)
157 if err == nil {
158 if _, err := io.Copy(os.Stdout, logs); err != nil {
159 log.Printf("Log pump error: %v", err)
160 }
161 logs.Close()
Lorenz Brun6df7c4f2020-12-21 15:02:00 +0100162 } else if err == ctx.Err() {
163 return // Exit if the context has been cancelled
Lorenz Bruned0503c2020-07-28 17:21:25 +0200164 } else {
165 log.Printf("Pod logs not ready yet: %v", err)
166 }
167 time.Sleep(1 * time.Second)
168 }
169 }()
170 for {
171 time.Sleep(1 * time.Second)
172 pod, err := clientSet.CoreV1().Pods("default").Get(ctx, podName, metav1.GetOptions{})
Lorenz Brun6df7c4f2020-12-21 15:02:00 +0100173 if err != nil && err == ctx.Err() {
174 return // Exit if the context has been cancelled
175 } else if err != nil {
Lorenz Bruned0503c2020-07-28 17:21:25 +0200176 log.Printf("Failed to get CTS pod: %v", err)
177 continue
178 }
179 if pod.Status.Phase == corev1.PodSucceeded {
180 return
181 }
182 if pod.Status.Phase == corev1.PodFailed {
183 log.Fatalf("CTS failed")
184 }
185 }
186}