forked from temporalio/temporal-worker-controller
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_builder.go
More file actions
348 lines (297 loc) · 12.2 KB
/
test_builder.go
File metadata and controls
348 lines (297 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
package testhelpers
import (
"time"
temporaliov1alpha1 "github.com/temporalio/temporal-worker-controller/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// TemporalWorkerDeploymentBuilder provides a fluent interface for building test TWD objects
type TemporalWorkerDeploymentBuilder struct {
twd *temporaliov1alpha1.TemporalWorkerDeployment
statusBuilder *StatusBuilder
}
// NewTemporalWorkerDeploymentBuilder creates a new builder with sensible defaults
func NewTemporalWorkerDeploymentBuilder() *TemporalWorkerDeploymentBuilder {
return &TemporalWorkerDeploymentBuilder{
twd: MakeTWDWithName("", ""),
}
}
// WithName sets the name
func (b *TemporalWorkerDeploymentBuilder) WithName(name string) *TemporalWorkerDeploymentBuilder {
b.twd.ObjectMeta.Name = name
b.twd.Name = name
return b
}
// WithNamespace sets the namespace
func (b *TemporalWorkerDeploymentBuilder) WithNamespace(namespace string) *TemporalWorkerDeploymentBuilder {
b.twd.ObjectMeta.Namespace = namespace
return b
}
// WithAllAtOnceStrategy sets the rollout strategy to all-at-once
func (b *TemporalWorkerDeploymentBuilder) WithAllAtOnceStrategy() *TemporalWorkerDeploymentBuilder {
b.twd.Spec.RolloutStrategy.Strategy = temporaliov1alpha1.UpdateAllAtOnce
return b
}
// WithProgressiveStrategy sets the rollout strategy to progressive with given steps
func (b *TemporalWorkerDeploymentBuilder) WithProgressiveStrategy(steps ...temporaliov1alpha1.RolloutStep) *TemporalWorkerDeploymentBuilder {
b.twd.Spec.RolloutStrategy.Strategy = temporaliov1alpha1.UpdateProgressive
b.twd.Spec.RolloutStrategy.Steps = steps
return b
}
// WithGate sets the rollout strategy have a gate workflow
func (b *TemporalWorkerDeploymentBuilder) WithGate(expectSuccess bool) *TemporalWorkerDeploymentBuilder {
if expectSuccess {
b.twd.Spec.RolloutStrategy.Gate = &temporaliov1alpha1.GateWorkflowConfig{WorkflowType: successTestWorkflowType}
} else {
b.twd.Spec.RolloutStrategy.Gate = &temporaliov1alpha1.GateWorkflowConfig{WorkflowType: failTestWorkflowType}
}
return b
}
// WithReplicas sets the number of replicas
func (b *TemporalWorkerDeploymentBuilder) WithReplicas(replicas int32) *TemporalWorkerDeploymentBuilder {
b.twd.Spec.Replicas = &replicas
return b
}
// WithVersion sets the worker version (creates HelloWorld pod spec)
func (b *TemporalWorkerDeploymentBuilder) WithVersion(version string) *TemporalWorkerDeploymentBuilder {
b.twd.Spec.Template = MakeHelloWorldPodSpec(version)
return b
}
// WithTemporalConnection sets the temporal connection name
func (b *TemporalWorkerDeploymentBuilder) WithTemporalConnection(connectionName string) *TemporalWorkerDeploymentBuilder {
b.twd.Spec.WorkerOptions.TemporalConnection = connectionName
return b
}
// WithTemporalNamespace sets the temporal namespace
func (b *TemporalWorkerDeploymentBuilder) WithTemporalNamespace(temporalNamespace string) *TemporalWorkerDeploymentBuilder {
b.twd.Spec.WorkerOptions.TemporalNamespace = temporalNamespace
return b
}
// WithLabels sets the labels
func (b *TemporalWorkerDeploymentBuilder) WithLabels(labels map[string]string) *TemporalWorkerDeploymentBuilder {
if b.twd.ObjectMeta.Labels == nil {
b.twd.ObjectMeta.Labels = make(map[string]string)
}
for k, v := range labels {
b.twd.ObjectMeta.Labels[k] = v
}
return b
}
// WithTargetVersionStatus sets the status to have a target version
func (b *TemporalWorkerDeploymentBuilder) WithTargetVersionStatus(imageName string, rampPercentage float32, healthy, createDeployment bool) *TemporalWorkerDeploymentBuilder {
if b.statusBuilder == nil {
b.statusBuilder = NewStatusBuilder()
}
b.statusBuilder.targetVersionBuilder = func(name string, namespace string) temporaliov1alpha1.TargetWorkerDeploymentVersion {
return MakeTargetVersion(namespace, name, imageName, rampPercentage, healthy, createDeployment)
}
return b
}
// WithCurrentVersionStatus sets the status to have a current version
func (b *TemporalWorkerDeploymentBuilder) WithCurrentVersionStatus(imageName string, healthy, createDeployment bool) *TemporalWorkerDeploymentBuilder {
if b.statusBuilder == nil {
b.statusBuilder = NewStatusBuilder()
}
b.statusBuilder.currentVersionBuilder = func(name string, namespace string) *temporaliov1alpha1.CurrentWorkerDeploymentVersion {
return MakeCurrentVersion(namespace, name, imageName, healthy, createDeployment)
}
return b
}
// Build returns the constructed TemporalWorkerDeployment
func (b *TemporalWorkerDeploymentBuilder) Build() *temporaliov1alpha1.TemporalWorkerDeployment {
// Set defaults if not already set
if b.twd.Spec.WorkerOptions.TemporalConnection == "" {
b.twd.Spec.WorkerOptions.TemporalConnection = b.twd.Name
}
if b.twd.ObjectMeta.Labels == nil {
b.twd.ObjectMeta.Labels = map[string]string{"app": "test-worker"}
}
if b.statusBuilder != nil {
b.twd.Status = *b.statusBuilder.
WithName(b.twd.Name).
WithNamespace(b.twd.Namespace).
Build()
}
return b.twd
}
// StatusBuilder provides a fluent interface for building expected status objects
// Versions will be built based on the TWD name and k8s namespace
type StatusBuilder struct {
name string
k8sNamespace string
targetVersionBuilder func(name string, namespace string) temporaliov1alpha1.TargetWorkerDeploymentVersion
currentVersionBuilder func(name string, namespace string) *temporaliov1alpha1.CurrentWorkerDeploymentVersion
deprecatedVersionsBuilder func(name string, namespace string) []*temporaliov1alpha1.DeprecatedWorkerDeploymentVersion
// ConflictToken, LastModifierIdentity, and VersionCount not currently tested
}
// NewStatusBuilder creates a new status builder
func NewStatusBuilder() *StatusBuilder {
return &StatusBuilder{}
}
// WithName sets the name
func (sb *StatusBuilder) WithName(name string) *StatusBuilder {
sb.name = name
return sb
}
// WithNamespace sets the namespace
func (sb *StatusBuilder) WithNamespace(k8sNamespace string) *StatusBuilder {
sb.k8sNamespace = k8sNamespace
return sb
}
// WithCurrentVersion sets the current version in the status
func (sb *StatusBuilder) WithCurrentVersion(imageName string, healthy, createDeployment bool) *StatusBuilder {
sb.currentVersionBuilder = func(name string, namespace string) *temporaliov1alpha1.CurrentWorkerDeploymentVersion {
return MakeCurrentVersion(namespace, name, imageName, healthy, createDeployment)
}
return sb
}
// WithTargetVersion sets the target version in the status.
// Target Version is required.
func (sb *StatusBuilder) WithTargetVersion(imageName string, rampPercentage float32, healthy bool, createDeployment bool) *StatusBuilder {
sb.targetVersionBuilder = func(name string, namespace string) temporaliov1alpha1.TargetWorkerDeploymentVersion {
return MakeTargetVersion(namespace, name, imageName, rampPercentage, healthy, createDeployment)
}
return sb
}
// Build returns the constructed status
func (sb *StatusBuilder) Build() *temporaliov1alpha1.TemporalWorkerDeploymentStatus {
if sb.targetVersionBuilder == nil {
return nil
}
ret := &temporaliov1alpha1.TemporalWorkerDeploymentStatus{
TargetVersion: sb.targetVersionBuilder(sb.name, sb.k8sNamespace),
}
if sb.currentVersionBuilder != nil {
ret.CurrentVersion = sb.currentVersionBuilder(sb.name, sb.k8sNamespace)
}
if sb.deprecatedVersionsBuilder != nil {
ret.DeprecatedVersions = sb.deprecatedVersionsBuilder(sb.name, sb.k8sNamespace)
}
return ret
}
type TestCase struct {
// If starting from a particular state, specify that in input.Status
twd *temporaliov1alpha1.TemporalWorkerDeployment
// TemporalWorkerDeploymentStatus only tracks the names of the Deployments for deprecated
// versions, so for test scenarios that start with existing deprecated version Deployments,
// specify the number of replicas for each deprecated build here.
deprecatedBuildReplicas map[string]int32
deprecatedBuildImages map[string]string
expectedStatus *temporaliov1alpha1.TemporalWorkerDeploymentStatus
// Time to delay before checking expected status
waitTime *time.Duration
}
func (tc *TestCase) GetTWD() *temporaliov1alpha1.TemporalWorkerDeployment {
return tc.twd
}
func (tc *TestCase) GetDeprecatedBuildReplicas() map[string]int32 {
return tc.deprecatedBuildReplicas
}
func (tc *TestCase) GetDeprecatedBuildImages() map[string]string {
return tc.deprecatedBuildImages
}
func (tc *TestCase) GetExpectedStatus() *temporaliov1alpha1.TemporalWorkerDeploymentStatus {
return tc.expectedStatus
}
func (tc *TestCase) GetWaitTime() *time.Duration {
return tc.waitTime
}
// TestCaseBuilder provides a fluent interface for building test cases
type TestCaseBuilder struct {
name string
k8sNamespace string
temporalNamespace string
twdBuilder *TemporalWorkerDeploymentBuilder
expectedStatusBuilder *StatusBuilder
deprecatedVersionInfos []DeprecatedVersionInfo
waitTime *time.Duration
}
// NewTestCase creates a new test case builder
func NewTestCase() *TestCaseBuilder {
return &TestCaseBuilder{
twdBuilder: NewTemporalWorkerDeploymentBuilder(),
expectedStatusBuilder: NewStatusBuilder(),
deprecatedVersionInfos: make([]DeprecatedVersionInfo, 0),
}
}
// NewTestCaseWithValues creates a new test case builder with the given values
func NewTestCaseWithValues(name, k8sNamespace, temporalNamespace string) *TestCaseBuilder {
return &TestCaseBuilder{
name: name,
k8sNamespace: k8sNamespace,
temporalNamespace: temporalNamespace,
twdBuilder: NewTemporalWorkerDeploymentBuilder(),
expectedStatusBuilder: NewStatusBuilder(),
deprecatedVersionInfos: make([]DeprecatedVersionInfo, 0),
}
}
// WithInput sets the input TWD
func (tcb *TestCaseBuilder) WithInput(twdBuilder *TemporalWorkerDeploymentBuilder) *TestCaseBuilder {
tcb.twdBuilder = twdBuilder
return tcb
}
// WithWaitTime sets the wait time. Use this if you are expecting no change to the initial status and want to ensure
// that after some time, there is still no change.
func (tcb *TestCaseBuilder) WithWaitTime(waitTime time.Duration) *TestCaseBuilder {
tcb.waitTime = &waitTime
return tcb
}
// DeprecatedVersionInfo defines the necessary information about a deprecated worker version, so that
// tests can recreate state that is not visible in the TemporalWorkerDeployment status
type DeprecatedVersionInfo struct {
image string
replicas int32
}
func NewDeprecatedVersionInfo(image string, replicas int32) DeprecatedVersionInfo {
return DeprecatedVersionInfo{
image: image,
replicas: replicas,
}
}
// WithDeprecatedBuilds adds deprecated build replicas and images, indexed by the build id that the given image would result in
func (tcb *TestCaseBuilder) WithDeprecatedBuilds(deprecatedVersionInfos ...DeprecatedVersionInfo) *TestCaseBuilder {
tcb.deprecatedVersionInfos = deprecatedVersionInfos
return tcb
}
// WithExpectedStatus sets the expected status
func (tcb *TestCaseBuilder) WithExpectedStatus(statusBuilder *StatusBuilder) *TestCaseBuilder {
tcb.expectedStatusBuilder = statusBuilder
return tcb
}
// Build returns the constructed test case
func (tcb *TestCaseBuilder) Build() TestCase {
ret := TestCase{
waitTime: tcb.waitTime,
twd: tcb.twdBuilder.
WithName(tcb.name).
WithNamespace(tcb.k8sNamespace).
WithTemporalConnection(tcb.name).
WithTemporalNamespace(tcb.temporalNamespace).
Build(),
deprecatedBuildReplicas: make(map[string]int32),
deprecatedBuildImages: make(map[string]string),
expectedStatus: tcb.expectedStatusBuilder.
WithName(tcb.name).
WithNamespace(tcb.k8sNamespace).
Build(),
}
for _, info := range tcb.deprecatedVersionInfos {
buildId := MakeBuildId(tcb.name, info.image, nil)
ret.deprecatedBuildReplicas[buildId] = info.replicas
ret.deprecatedBuildImages[buildId] = info.image
}
return ret
}
// BuildWithValues populates all fields affected by test name, k8s namespace, and temporal namespace and returns the constructed test case
func (tcb *TestCaseBuilder) BuildWithValues(name, k8sNamespace, temporalNamespace string) TestCase {
tcb.name = name
tcb.k8sNamespace = k8sNamespace
tcb.temporalNamespace = temporalNamespace
return tcb.Build()
}
// ProgressiveStep creates a progressive rollout step
func ProgressiveStep(rampPercentage float32, pauseDuration time.Duration) temporaliov1alpha1.RolloutStep {
return temporaliov1alpha1.RolloutStep{
RampPercentage: rampPercentage,
PauseDuration: metav1.Duration{Duration: pauseDuration},
}
}