blob: c0adc75bab5f9cadedbebe07ec8436260673f871 [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")
Serge Bazanskibe742842022-04-04 13:18:50 +0200109 // TODO(q3k): use SOCKS proxy instead.
110 clientSet, err := e2e.GetKubeClientSet(cl, cl.Ports[uint16(common.KubernetesAPIWrappedPort)])
Lorenz Bruned0503c2020-07-28 17:21:25 +0200111 if err != nil {
112 log.Fatalf("Failed to get clientSet: %v", err)
113 }
114 log.Println("Credentials available")
115
116 saName := "cts"
117 ctsSA := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: saName}}
118 for {
119 if _, err := clientSet.CoreV1().ServiceAccounts("default").Create(ctx, ctsSA, metav1.CreateOptions{}); err != nil {
120 log.Printf("Failed to create ServiceAccount: %v", err)
121 time.Sleep(1 * time.Second)
122 continue
123 }
124 break
125 }
126 ctsRoleBinding := &rbacv1.ClusterRoleBinding{
127 ObjectMeta: metav1.ObjectMeta{
128 Name: saName,
129 },
130 Subjects: []rbacv1.Subject{
131 {
132 Namespace: "default",
133 Name: saName,
134 Kind: rbacv1.ServiceAccountKind,
135 },
136 },
137 RoleRef: rbacv1.RoleRef{
138 Kind: "ClusterRole",
139 Name: "cluster-admin",
140 },
141 }
142 podName := "cts"
143 if _, err := clientSet.RbacV1().ClusterRoleBindings().Create(ctx, ctsRoleBinding, metav1.CreateOptions{}); err != nil {
144 log.Fatalf("Failed to create ClusterRoleBinding: %v", err)
145 }
146 for {
147 if _, err := clientSet.CoreV1().Pods("default").Create(ctx, makeCTSPodSpec(podName, saName), metav1.CreateOptions{}); err != nil {
148 log.Printf("Failed to create Pod: %v", err)
149 time.Sleep(1 * time.Second)
150 continue
151 }
152 break
153 }
154 var logs io.ReadCloser
155 go func() {
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200156 // This loops the whole .Stream()/io.Copy process because the API
157 // sometimes returns streams that immediately return EOF
Lorenz Bruned0503c2020-07-28 17:21:25 +0200158 for {
159 logs, err = clientSet.CoreV1().Pods("default").GetLogs(podName, &corev1.PodLogOptions{Follow: true}).Stream(ctx)
160 if err == nil {
161 if _, err := io.Copy(os.Stdout, logs); err != nil {
162 log.Printf("Log pump error: %v", err)
163 }
164 logs.Close()
Lorenz Brun6df7c4f2020-12-21 15:02:00 +0100165 } else if err == ctx.Err() {
166 return // Exit if the context has been cancelled
Lorenz Bruned0503c2020-07-28 17:21:25 +0200167 } else {
168 log.Printf("Pod logs not ready yet: %v", err)
169 }
170 time.Sleep(1 * time.Second)
171 }
172 }()
173 for {
174 time.Sleep(1 * time.Second)
175 pod, err := clientSet.CoreV1().Pods("default").Get(ctx, podName, metav1.GetOptions{})
Lorenz Brun6df7c4f2020-12-21 15:02:00 +0100176 if err != nil && err == ctx.Err() {
177 return // Exit if the context has been cancelled
178 } else if err != nil {
Lorenz Bruned0503c2020-07-28 17:21:25 +0200179 log.Printf("Failed to get CTS pod: %v", err)
180 continue
181 }
182 if pod.Status.Phase == corev1.PodSucceeded {
183 return
184 }
185 if pod.Status.Phase == corev1.PodFailed {
186 log.Fatalf("CTS failed")
187 }
188 }
189}