blob: de0bbccecd41475f630d3556a7d47fa96e8a273b [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Lorenz Bruned0503c2020-07-28 17:21:25 +02002// SPDX-License-Identifier: Apache-2.0
Lorenz Bruned0503c2020-07-28 17:21:25 +02003
Serge Bazanski216fe7b2021-05-21 18:36:16 +02004// This package launches a Metropolis cluster with two nodes and spawns in the
5// CTS container. Then it streams its output to the console. When the CTS has
6// finished it exits with the appropriate error code.
Lorenz Bruned0503c2020-07-28 17:21:25 +02007package main
8
9import (
10 "context"
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +020011 "errors"
Lorenz Bruned0503c2020-07-28 17:21:25 +020012 "io"
13 "log"
14 "os"
15 "os/signal"
16 "strings"
17 "syscall"
18 "time"
19
Lorenz Bruned0503c2020-07-28 17:21:25 +020020 corev1 "k8s.io/api/core/v1"
21 rbacv1 "k8s.io/api/rbac/v1"
22 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020024 mlaunch "source.monogon.dev/metropolis/test/launch"
Lorenz Bruned0503c2020-07-28 17:21:25 +020025)
26
Serge Bazanski216fe7b2021-05-21 18:36:16 +020027// makeCTSPodSpec generates a spec for a standalone pod running the Kubernetes
28// CTS. It also sets the test configuration for the Kubernetes E2E test suite
29// to only run CTS tests and excludes known-broken ones.
Lorenz Bruned0503c2020-07-28 17:21:25 +020030func makeCTSPodSpec(name string, saName string) *corev1.Pod {
31 skipRegexes := []string{
Serge Bazanski216fe7b2021-05-21 18:36:16 +020032 // hostNetworking cannot be supported since we run different network
33 // stacks for the host and containers
Lorenz Bruned0503c2020-07-28 17:21:25 +020034 "should function for node-pod communication",
Serge Bazanski216fe7b2021-05-21 18:36:16 +020035 // gVisor misreports statfs() syscalls:
36 // https://github.com/google/gvisor/issues/3339
Lorenz Bruned0503c2020-07-28 17:21:25 +020037 `should support \((non-)?root,`,
38 "volume on tmpfs should have the correct mode",
39 "volume on default medium should have the correct mode",
Serge Bazanski216fe7b2021-05-21 18:36:16 +020040 // gVisor doesn't support the full Linux privilege machinery including
41 // SUID and NewPrivs:
42 // https://github.com/google/gvisor/issues/189#issuecomment-481064000
Lorenz Bruned0503c2020-07-28 17:21:25 +020043 "should run the container as unprivileged when false",
44 }
45 return &corev1.Pod{
46 ObjectMeta: metav1.ObjectMeta{
47 Name: name,
48 Labels: map[string]string{
49 "name": name,
50 },
51 },
52 Spec: corev1.PodSpec{
53 Containers: []corev1.Container{
54 {
55 Name: "cts",
Serge Bazanski77cb6c52020-12-19 00:09:22 +010056 Image: "bazel/metropolis/test/e2e/k8s_cts:k8s_cts_image",
Lorenz Bruned0503c2020-07-28 17:21:25 +020057 Args: []string{
58 "-cluster-ip-range=10.0.0.0/17",
59 "-dump-systemd-journal=false",
60 "-ginkgo.focus=\\[Conformance\\]",
61 "-ginkgo.skip=" + strings.Join(skipRegexes, "|"),
62 "-test.parallel=8",
63 },
64 ImagePullPolicy: corev1.PullNever,
65 },
66 },
Serge Bazanski216fe7b2021-05-21 18:36:16 +020067 // Tolerate all taints, otherwise the CTS likes to self-evict.
68 Tolerations: []corev1.Toleration{{
Lorenz Bruned0503c2020-07-28 17:21:25 +020069 Operator: "Exists",
70 }},
Serge Bazanski216fe7b2021-05-21 18:36:16 +020071 // Don't evict the CTS pod.
72 PriorityClassName: "system-cluster-critical",
Lorenz Bruned0503c2020-07-28 17:21:25 +020073 RestartPolicy: corev1.RestartPolicyNever,
74 ServiceAccountName: saName,
75 },
76 }
77}
78
79func main() {
80 sigs := make(chan os.Signal, 1)
81 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
82 ctx, cancel := context.WithCancel(context.Background())
83 go func() {
Lorenz Brun6df7c4f2020-12-21 15:02:00 +010084 sig := <-sigs
85 log.Printf("Got signal %s, aborting test", sig)
Lorenz Bruned0503c2020-07-28 17:21:25 +020086 cancel()
87 }()
88
Serge Bazanskif055a7f2021-04-13 16:22:33 +020089 // TODO(q3k): bump up number of nodes after multi-node workflow gets reimplemented.
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020090 cl, err := mlaunch.LaunchCluster(ctx, mlaunch.ClusterOptions{NumNodes: 1})
Lorenz Bruned0503c2020-07-28 17:21:25 +020091 if err != nil {
92 log.Fatalf("Failed to launch cluster: %v", err)
93 }
94 log.Println("Cluster initialized")
Lorenz Brun8f1254d2025-01-28 14:10:05 +010095 clientSet, _, err := cl.GetKubeClientSet()
Lorenz Bruned0503c2020-07-28 17:21:25 +020096 if err != nil {
97 log.Fatalf("Failed to get clientSet: %v", err)
98 }
99 log.Println("Credentials available")
100
101 saName := "cts"
102 ctsSA := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: saName}}
103 for {
104 if _, err := clientSet.CoreV1().ServiceAccounts("default").Create(ctx, ctsSA, metav1.CreateOptions{}); err != nil {
105 log.Printf("Failed to create ServiceAccount: %v", err)
106 time.Sleep(1 * time.Second)
107 continue
108 }
109 break
110 }
111 ctsRoleBinding := &rbacv1.ClusterRoleBinding{
112 ObjectMeta: metav1.ObjectMeta{
113 Name: saName,
114 },
115 Subjects: []rbacv1.Subject{
116 {
117 Namespace: "default",
118 Name: saName,
119 Kind: rbacv1.ServiceAccountKind,
120 },
121 },
122 RoleRef: rbacv1.RoleRef{
123 Kind: "ClusterRole",
124 Name: "cluster-admin",
125 },
126 }
127 podName := "cts"
128 if _, err := clientSet.RbacV1().ClusterRoleBindings().Create(ctx, ctsRoleBinding, metav1.CreateOptions{}); err != nil {
129 log.Fatalf("Failed to create ClusterRoleBinding: %v", err)
130 }
131 for {
132 if _, err := clientSet.CoreV1().Pods("default").Create(ctx, makeCTSPodSpec(podName, saName), metav1.CreateOptions{}); err != nil {
133 log.Printf("Failed to create Pod: %v", err)
134 time.Sleep(1 * time.Second)
135 continue
136 }
137 break
138 }
139 var logs io.ReadCloser
140 go func() {
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200141 // This loops the whole .Stream()/io.Copy process because the API
142 // sometimes returns streams that immediately return EOF
Lorenz Bruned0503c2020-07-28 17:21:25 +0200143 for {
144 logs, err = clientSet.CoreV1().Pods("default").GetLogs(podName, &corev1.PodLogOptions{Follow: true}).Stream(ctx)
145 if err == nil {
146 if _, err := io.Copy(os.Stdout, logs); err != nil {
147 log.Printf("Log pump error: %v", err)
148 }
149 logs.Close()
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200150 } else if errors.Is(err, ctx.Err()) {
Lorenz Brun6df7c4f2020-12-21 15:02:00 +0100151 return // Exit if the context has been cancelled
Lorenz Bruned0503c2020-07-28 17:21:25 +0200152 } else {
153 log.Printf("Pod logs not ready yet: %v", err)
154 }
155 time.Sleep(1 * time.Second)
156 }
157 }()
158 for {
159 time.Sleep(1 * time.Second)
160 pod, err := clientSet.CoreV1().Pods("default").Get(ctx, podName, metav1.GetOptions{})
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200161 if err != nil && errors.Is(err, ctx.Err()) {
Lorenz Brun6df7c4f2020-12-21 15:02:00 +0100162 return // Exit if the context has been cancelled
163 } else if err != nil {
Lorenz Bruned0503c2020-07-28 17:21:25 +0200164 log.Printf("Failed to get CTS pod: %v", err)
165 continue
166 }
167 if pod.Status.Phase == corev1.PodSucceeded {
168 return
169 }
170 if pod.Status.Phase == corev1.PodFailed {
171 log.Fatalf("CTS failed")
172 }
173 }
174}