Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ws-manager-mk2/controllers/suite_test.go
2498 views
1
// Copyright (c) 2022 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 controllers
6
7
import (
8
"context"
9
"path/filepath"
10
"testing"
11
"time"
12
13
. "github.com/onsi/ginkgo/v2"
14
. "github.com/onsi/gomega"
15
corev1 "k8s.io/api/core/v1"
16
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17
"k8s.io/client-go/kubernetes/scheme"
18
ctrl "sigs.k8s.io/controller-runtime"
19
"sigs.k8s.io/controller-runtime/pkg/client"
20
"sigs.k8s.io/controller-runtime/pkg/envtest"
21
logf "sigs.k8s.io/controller-runtime/pkg/log"
22
"sigs.k8s.io/controller-runtime/pkg/log/zap"
23
"sigs.k8s.io/controller-runtime/pkg/metrics"
24
25
"github.com/gitpod-io/gitpod/common-go/util"
26
"github.com/gitpod-io/gitpod/ws-manager/api/config"
27
workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
28
//+kubebuilder:scaffold:imports
29
)
30
31
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
32
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
33
34
const (
35
timeout = time.Second * 20
36
duration = time.Second * 2
37
interval = time.Millisecond * 250
38
secretsNamespace = "workspace-secrets"
39
)
40
41
// var cfg *rest.Config
42
var k8sClient client.Client
43
var testEnv *envtest.Environment
44
45
func TestAPIs(t *testing.T) {
46
RegisterFailHandler(Fail)
47
48
RunSpecs(t, "Controller Suite")
49
}
50
51
var (
52
ctx context.Context
53
cancel context.CancelFunc
54
wsMetrics *controllerMetrics
55
RegisterSubscriber func(func(*workspacev1.Workspace))
56
)
57
58
var _ = BeforeSuite(func() {
59
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
60
61
By("bootstrapping test environment")
62
testEnv = &envtest.Environment{
63
ControlPlaneStartTimeout: 1 * time.Minute,
64
ControlPlaneStopTimeout: 1 * time.Minute,
65
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
66
ErrorIfCRDPathMissing: true,
67
}
68
69
cfg, err := testEnv.Start()
70
Expect(err).NotTo(HaveOccurred())
71
Expect(cfg).NotTo(BeNil())
72
73
err = workspacev1.AddToScheme(scheme.Scheme)
74
Expect(err).NotTo(HaveOccurred())
75
76
//+kubebuilder:scaffold:scheme
77
78
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
79
Expect(err).NotTo(HaveOccurred())
80
Expect(k8sClient).NotTo(BeNil())
81
82
/*
83
One thing that this autogenerated file is missing, however, is a way to actually start your controller.
84
The code above will set up a client for interacting with your custom Kind,
85
but will not be able to test your controller behavior.
86
If you want to test your custom controller logic, you’ll need to add some familiar-looking manager logic
87
to your BeforeSuite() function, so you can register your custom controller to run on this test cluster.
88
You may notice that the code below runs your controller with nearly identical logic to your CronJob project’s main.go!
89
The only difference is that the manager is started in a separate goroutine so it does not block the cleanup of envtest
90
when you’re done running your tests.
91
Note that we set up both a "live" k8s client and a separate client from the manager. This is because when making
92
assertions in tests, you generally want to assert against the live state of the API server. If you use the client
93
from the manager (`k8sManager.GetClient`), you'd end up asserting against the contents of the cache instead, which is
94
slower and can introduce flakiness into your tests. We could use the manager's `APIReader` to accomplish the same
95
thing, but that would leave us with two clients in our test assertions and setup (one for reading, one for writing),
96
and it'd be easy to make mistakes.
97
Note that we keep the reconciler running against the manager's cache client, though -- we want our controller to
98
behave as it would in production, and we use features of the cache (like indicies) in our controller which aren't
99
available when talking directly to the API server.
100
*/
101
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
102
Scheme: scheme.Scheme,
103
})
104
Expect(err).ToNot(HaveOccurred())
105
106
SetupIndexer(k8sManager)
107
108
conf := newTestConfig()
109
maintenance := &fakeMaintenance{enabled: false}
110
wsReconciler, err := NewWorkspaceReconciler(k8sManager.GetClient(), k8sManager.GetConfig(), k8sManager.GetScheme(), k8sManager.GetEventRecorderFor("workspace"), &conf, metrics.Registry, maintenance)
111
wsMetrics = wsReconciler.metrics
112
Expect(err).ToNot(HaveOccurred())
113
Expect(wsReconciler.SetupWithManager(k8sManager)).To(Succeed())
114
115
timeoutReconciler, err := NewTimeoutReconciler(k8sManager.GetClient(), k8sManager.GetEventRecorderFor("workspace"), conf, maintenance)
116
Expect(err).ToNot(HaveOccurred())
117
Expect(timeoutReconciler.SetupWithManager(k8sManager)).To(Succeed())
118
119
ctx, cancel = context.WithCancel(context.Background())
120
subscriberReconciler, err := NewSubscriberReconciler(k8sManager.GetClient(), &conf)
121
Expect(err).ToNot(HaveOccurred())
122
Expect(subscriberReconciler.SetupWithManager(ctx, k8sManager)).To(Succeed())
123
RegisterSubscriber = func(onReconcile func(*workspacev1.Workspace)) {
124
subscriberReconciler.OnReconcile = func(ctx context.Context, ws *workspacev1.Workspace) {
125
onReconcile(ws)
126
}
127
}
128
129
_ = createNamespace(secretsNamespace)
130
131
go func() {
132
defer GinkgoRecover()
133
err := k8sManager.Start(ctx)
134
Expect(err).ToNot(HaveOccurred(), "failed to run manager")
135
}()
136
137
})
138
139
func newTestConfig() config.Configuration {
140
return config.Configuration{
141
GitpodHostURL: "gitpod.io",
142
HeartbeatInterval: util.Duration(30 * time.Second),
143
Namespace: "default",
144
SecretsNamespace: secretsNamespace,
145
SeccompProfile: "default.json",
146
Timeouts: config.WorkspaceTimeoutConfiguration{
147
AfterClose: util.Duration(1 * time.Minute),
148
Initialization: util.Duration(30 * time.Minute),
149
TotalStartup: util.Duration(45 * time.Minute),
150
RegularWorkspace: util.Duration(60 * time.Minute),
151
MaxLifetime: util.Duration(36 * time.Hour),
152
HeadlessWorkspace: util.Duration(90 * time.Minute),
153
Stopping: util.Duration(60 * time.Minute),
154
ContentFinalization: util.Duration(55 * time.Minute),
155
Interrupted: util.Duration(5 * time.Minute),
156
},
157
WorkspaceClasses: map[string]*config.WorkspaceClass{
158
"default": {
159
Name: "default",
160
},
161
},
162
WorkspaceURLTemplate: "{{ .ID }}-{{ .Prefix }}-{{ .Host }}",
163
PodRecreationMaxRetries: 3,
164
PodRecreationBackoff: util.Duration(500 * time.Millisecond),
165
}
166
}
167
168
type fakeMaintenance struct {
169
enabled bool
170
}
171
172
func (f *fakeMaintenance) IsEnabled(context.Context) bool {
173
return f.enabled
174
}
175
176
func createNamespace(name string) *corev1.Namespace {
177
GinkgoHelper()
178
179
namespace := &corev1.Namespace{
180
ObjectMeta: metav1.ObjectMeta{
181
Name: name,
182
},
183
}
184
185
Expect(k8sClient.Create(ctx, namespace)).To(Succeed())
186
return namespace
187
}
188
189
var _ = AfterSuite(func() {
190
cancel()
191
By("tearing down the test environment")
192
err := testEnv.Stop()
193
Expect(err).NotTo(HaveOccurred())
194
})
195
196