Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/test/pkg/integration/setup.go
2498 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package integration
6
7
import (
8
"context"
9
"flag"
10
"fmt"
11
"math/rand"
12
"os"
13
"strings"
14
"testing"
15
"text/tabwriter"
16
"time"
17
18
corev1 "k8s.io/api/core/v1"
19
"k8s.io/apimachinery/pkg/util/wait"
20
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
21
"k8s.io/client-go/tools/clientcmd"
22
"k8s.io/klog/v2"
23
"sigs.k8s.io/e2e-framework/klient"
24
"sigs.k8s.io/e2e-framework/pkg/env"
25
"sigs.k8s.io/e2e-framework/pkg/envconf"
26
"sigs.k8s.io/e2e-framework/pkg/flags"
27
)
28
29
func SkipWithoutUsername(t *testing.T, username string) {
30
if username == "" {
31
t.Skip("Skipping because requires a username")
32
}
33
}
34
35
func SkipWithoutUserToken(t *testing.T, userToken string) {
36
if userToken == "" {
37
t.Skip("Skipping because requires a user token")
38
}
39
}
40
41
func SkipWithoutEnterpriseLicense(t *testing.T, enterpise bool) {
42
if !enterpise {
43
t.Skip("Skipping because requires enterprise license")
44
}
45
}
46
47
func EnsureUserExists(t *testing.T, username string, api *ComponentAPI) string {
48
if username == "" {
49
t.Logf("no username provided, creating temporary one")
50
rand.Seed(time.Now().UnixNano())
51
randN := rand.Intn(1000)
52
newUser := fmt.Sprintf("johndoe%d", randN)
53
userId, err := CreateUser(newUser, false, api)
54
if err != nil {
55
t.Fatalf("cannot create user: %q", err)
56
}
57
t.Cleanup(func() {
58
err := DeleteUser(userId, api)
59
if err != nil {
60
t.Fatalf("error deleting user %q", err)
61
}
62
})
63
t.Logf("user '%s' with ID %s created", newUser, userId)
64
return newUser
65
}
66
return username
67
}
68
69
func Setup(ctx context.Context) (string, string, env.Environment, bool, string, bool) {
70
var (
71
username string
72
enterprise bool
73
gitlab bool
74
waitGitpodReady time.Duration
75
76
namespace string
77
kubeconfig string
78
feature string
79
assess string
80
parallel bool
81
82
labels = make(flags.LabelsMap)
83
skipLabels = make(flags.LabelsMap)
84
)
85
86
flagset := flag.CommandLine
87
klog.InitFlags(flagset)
88
89
defaultKubeConfig := os.Getenv("KUBE_CONFIG")
90
if defaultKubeConfig == "" {
91
defaultKubeConfig = "/home/gitpod/.kube/config"
92
}
93
flagset.StringVar(&username, "username", os.Getenv("USER_NAME"), "username to execute the tests with. Chooses one automatically if left blank.")
94
flagset.BoolVar(&enterprise, "enterprise", false, "whether to test enterprise features. requires enterprise lisence installed.")
95
flagset.BoolVar(&gitlab, "gitlab", false, "whether to test gitlab integration.")
96
flagset.BoolVar(&parallel, "parallel-features", false, "Run test features in parallel")
97
flagset.DurationVar(&waitGitpodReady, "wait-gitpod-timeout", 5*time.Minute, `wait time for Gitpod components before starting integration test`)
98
flagset.StringVar(&namespace, "namespace", "", "Kubernetes cluster namespaces to use")
99
flagset.StringVar(&kubeconfig, "kubeconfig", defaultKubeConfig, "The path to the kubeconfig file")
100
flagset.StringVar(&feature, "feature", "", "Regular expression that targets features to test")
101
flagset.StringVar(&assess, "assess", "", "Regular expression that targets assertive steps to run")
102
flagset.Var(&labels, "labels", "Comma-separated key/value pairs to filter tests by labels")
103
flagset.Var(&skipLabels, "skip-labels", "Comma-separated key/value pairs to skip tests by labels")
104
105
if err := flagset.Parse(os.Args[1:]); err != nil {
106
klog.Fatalf("cannot parse flags: %v", err)
107
}
108
109
e := envconf.New()
110
if assess != "" {
111
e.WithAssessmentRegex(assess)
112
}
113
if feature != "" {
114
e.WithFeatureRegex(feature)
115
}
116
if parallel {
117
e.WithParallelTestEnabled()
118
}
119
120
client, err := klient.NewWithKubeConfigFile(kubeconfig)
121
if err != nil {
122
klog.Fatalf("unexpected error: %v", err)
123
}
124
125
e.WithClient(client)
126
e.WithLabels(labels)
127
e.WithSkipLabels(skipLabels)
128
e.WithNamespace(namespace)
129
130
// use the namespace from the CurrentContext
131
if namespace == "" {
132
ns, err := getNamespace(kubeconfig)
133
if err != nil {
134
klog.Fatalf("unexpected error obtaining context namespace: %v", err)
135
}
136
e.WithNamespace(ns)
137
}
138
139
testenv, err := env.NewWithContext(ctx, e)
140
if err != nil {
141
klog.Fatalf("unexpected error: %v", err)
142
}
143
testenv.Setup(
144
waitOnGitpodRunning(e.Namespace(), waitGitpodReady),
145
)
146
147
return username, e.Namespace(), testenv, enterprise, kubeconfig, gitlab
148
}
149
150
type component struct {
151
name string
152
labelKey string
153
}
154
155
func (c component) Matches(pod corev1.Pod) bool {
156
key := "component"
157
if c.labelKey != "" {
158
key = c.labelKey
159
}
160
return pod.Labels[key] == c.name
161
}
162
163
var (
164
components = []component{
165
{name: "agent-smith"},
166
{name: "blobserve"},
167
{name: "content-service"},
168
{name: "dashboard"},
169
{name: "ide-proxy"},
170
{name: "ide-service"},
171
{name: "image-builder-mk3"},
172
{name: "minio", labelKey: "app.kubernetes.io/name"},
173
{name: "mysql", labelKey: "app.kubernetes.io/name"},
174
{name: "node-labeler"},
175
{name: "proxy"},
176
{name: "public-api-server"},
177
{name: "redis"},
178
{name: "registry-facade"},
179
{name: "server"},
180
{name: "spicedb"},
181
{name: "usage"},
182
{name: "ws-daemon"},
183
{name: "ws-manager-mk2"},
184
{name: "ws-manager-bridge"},
185
{name: "ws-proxy"},
186
}
187
)
188
189
func waitOnGitpodRunning(namespace string, waitTimeout time.Duration) env.Func {
190
klog.V(2).Info("Checking status of Gitpod components...")
191
return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
192
client := cfg.Client()
193
err := wait.PollImmediate(1*time.Second, waitTimeout, func() (bool, error) {
194
ready, reason, err := isPreviewReady(client, namespace)
195
if err != nil {
196
klog.Errorf("error checking if preview is ready: %v", err)
197
return false, nil
198
}
199
if !ready {
200
klog.Warningf("preview is not (yet) ready: %s", reason)
201
return false, nil
202
}
203
204
klog.V(2).Info("All Gitpod components are running...")
205
return true, nil
206
})
207
if err != nil {
208
return ctx, nil
209
}
210
211
return ctx, nil
212
}
213
}
214
215
func logGitpodStatus(t *testing.T, client klient.Client, namespace string) {
216
var allPods corev1.PodList
217
err := client.Resources(namespace).List(context.Background(), &allPods)
218
if err != nil {
219
t.Logf("failed to list pods to log gitpod status: %v", err)
220
return
221
}
222
223
var buf strings.Builder
224
tw := tabwriter.NewWriter(&buf, 0, 0, 1, ' ', 0)
225
_, _ = tw.Write([]byte("Component\tPod\tReady\tStatus\tRestarts\tAge\n"))
226
227
for _, component := range components {
228
var pods []corev1.Pod
229
for _, pod := range allPods.Items {
230
if component.Matches(pod) {
231
pods = append(pods, pod)
232
}
233
}
234
for _, p := range pods {
235
var restarts int
236
var ready int
237
for _, c := range p.Status.ContainerStatuses {
238
restarts += int(c.RestartCount)
239
if c.Ready {
240
ready += 1
241
}
242
}
243
var age *time.Duration
244
if p.Status.StartTime != nil {
245
s := time.Since(p.Status.StartTime.Time).Round(time.Second)
246
age = &s
247
}
248
_, _ = tw.Write([]byte(fmt.Sprintf("%s\t%s\t%d/%d\t%v\t%d\t%v\n", component.name, p.Name, ready, len(p.Status.ContainerStatuses), p.Status.Phase, restarts, age)))
249
}
250
}
251
tw.Flush()
252
t.Logf("Gitpod components status:\n" + buf.String())
253
}
254
255
func isPreviewReady(client klient.Client, namespace string) (ready bool, reason string, err error) {
256
ready = true
257
reasons := make(map[component]string)
258
var allPods corev1.PodList
259
err = client.Resources(namespace).List(context.Background(), &allPods)
260
if err != nil {
261
return false, "", fmt.Errorf("failed to list pods: %w", err)
262
}
263
for _, component := range components {
264
compReady, reason := isComponentReady(client, namespace, component, allPods)
265
if !compReady {
266
klog.Warningf("no pod ready for component %v: %s", component.name, reason)
267
ready = false
268
reasons[component] = reason
269
continue
270
}
271
}
272
273
if !ready {
274
var reasonList []string
275
for component, reason := range reasons {
276
reasonList = append(reasonList, fmt.Sprintf("%s: %s", component, reason))
277
}
278
return false, strings.Join(reasonList, ", "), nil
279
}
280
281
return true, "", nil
282
}
283
284
func isComponentReady(client klient.Client, namespace string, component component, allPods corev1.PodList) (ready bool, reason string) {
285
var pods []corev1.Pod
286
for _, pod := range allPods.Items {
287
if component.Matches(pod) {
288
pods = append(pods, pod)
289
}
290
}
291
292
if len(pods) == 0 {
293
return false, "no pod found"
294
}
295
296
for _, p := range pods {
297
var isReady bool
298
for _, cond := range p.Status.Conditions {
299
if cond.Type == corev1.PodReady {
300
isReady = cond.Status == corev1.ConditionTrue
301
if !isReady {
302
return false, fmt.Sprintf("pod %s is not ready: %v", p.Name, p.Status)
303
}
304
break
305
}
306
}
307
if !isReady {
308
return false, fmt.Sprintf("pod %s has no ready condition: %v", p.Name, p.Status)
309
}
310
}
311
312
return true, ""
313
}
314
315
func getNamespace(path string) (string, error) {
316
var cfg clientcmd.ClientConfig
317
318
switch path {
319
case "":
320
loadingrules := clientcmd.NewDefaultClientConfigLoadingRules()
321
cfg = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingrules,
322
&clientcmd.ConfigOverrides{})
323
default:
324
cfg = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
325
&clientcmd.ClientConfigLoadingRules{ExplicitPath: path},
326
&clientcmd.ConfigOverrides{})
327
}
328
329
namespace, _, err := cfg.Namespace()
330
if err != nil {
331
return "", err
332
}
333
334
return namespace, nil
335
}
336
337