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