forked from mongodb/mongodb-kubernetes-operator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreadiness_test.go
320 lines (303 loc) · 10.5 KB
/
readiness_test.go
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
package main
import (
"bytes"
"context"
"encoding/json"
"io"
"os"
"testing"
"time"
"github.com/mongodb/mongodb-kubernetes-operator/cmd/readiness/testdata"
"github.com/mongodb/mongodb-kubernetes-operator/pkg/readiness/config"
"github.com/mongodb/mongodb-kubernetes-operator/pkg/readiness/health"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
"github.com/stretchr/testify/assert"
)
// TestDeadlockDetection verifies that if the agent is stuck in "WaitAllRsMembersUp" phase (started > 15 seconds ago)
// then the function returns "ready"
func TestDeadlockDetection(t *testing.T) {
ctx := context.Background()
type TestConfig struct {
conf config.Config
isErrorExpected bool
isReadyExpected bool
}
tests := map[string]TestConfig{
"Ready but deadlocked on WaitAllRsMembersUp": {
conf: testConfig("testdata/health-status-deadlocked.json"),
isReadyExpected: true,
},
"Ready but deadlocked on WaitCanUpdate while changing the versions with multiple plans": {
conf: testConfig("testdata/health-status-deadlocked-with-prev-config.json"),
isReadyExpected: true,
},
"Ready but deadlocked on WaitHasCorrectAutomationCredentials (HELP-39937, HELP-39966)": {
conf: testConfig("testdata/health-status-deadlocked-waiting-for-correct-automation-credentials.json"),
isReadyExpected: true,
},
"Ready and no deadlock detected": {
conf: testConfig("testdata/health-status-no-deadlock.json"),
isReadyExpected: true,
},
"Ready and positive scenario": {
conf: testConfig("testdata/health-status-ok.json"),
isReadyExpected: true,
},
"Ready and Pod readiness is correctly checked when no ReplicationStatus is present on the file": {
conf: testConfig("testdata/health-status-no-replication.json"),
isReadyExpected: true,
},
"Ready and MongoDB replication state is reported by agents": {
conf: testConfig("testdata/health-status-ok-no-replica-status.json"),
isReadyExpected: true,
},
"Not Ready If replication state is not PRIMARY or SECONDARY, Pod is not ready": {
conf: testConfig("testdata/health-status-not-readable-state.json"),
isReadyExpected: false,
},
"Not Ready because of less than 15 seconds passed by after the health file update": {
conf: testConfig("testdata/health-status-pending.json"),
isReadyExpected: false,
},
"Not Ready because there are no plans": {
conf: testConfig("testdata/health-status-no-plans.json"),
isReadyExpected: false,
},
"Not Ready because there are no statuses": {
conf: testConfig("testdata/health-status-no-plans.json"),
isReadyExpected: false,
},
"Not Ready because there are no processes": {
conf: testConfig("testdata/health-status-no-processes.json"),
isReadyExpected: false,
},
"Not Ready because mongod is down for 90 seconds": {
conf: testConfigWithMongoUp("testdata/health-status-ok.json", time.Second*90),
isReadyExpected: false,
},
"Not Ready because mongod is down for 1 hour": {
conf: testConfigWithMongoUp("testdata/health-status-ok.json", time.Hour*1),
isReadyExpected: false,
},
"Not Ready because mongod is down for 2 days": {
conf: testConfigWithMongoUp("testdata/health-status-ok.json", time.Hour*48),
isReadyExpected: false,
},
"Ready and mongod is up for 30 seconds": {
conf: testConfigWithMongoUp("testdata/health-status-ok.json", time.Second*30),
isReadyExpected: true,
},
"Ready and mongod is up for 1 second": {
conf: testConfigWithMongoUp("testdata/health-status-ok.json", time.Second*30),
isReadyExpected: true,
},
"Not Ready because of mongod bootstrap errors": {
conf: testConfigWithMongoUp("testdata/health-status-error-tls.json", time.Second*30),
isReadyExpected: false,
},
"Not Ready because of waiting on an upgrade start in a recomputed plan (a real scenario for an interrupted start in EA)": {
conf: testConfigWithMongoUp("testdata/health-status-enterprise-upgrade-interrupted.json", time.Second*30),
isReadyExpected: false,
},
}
for testName := range tests {
testConfig := tests[testName]
t.Run(testName, func(t *testing.T) {
ready, err := isPodReady(ctx, testConfig.conf)
if testConfig.isErrorExpected {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, testConfig.isReadyExpected, ready)
})
}
}
func TestObtainingCurrentStep(t *testing.T) {
noDeadlockHealthExample, _ := parseHealthStatus(testConfig("testdata/health-status-no-deadlock.json").HealthStatusReader)
now := time.Now()
tenMinutesAgo := time.Now().Add(-time.Minute * 10)
type TestConfig struct {
processStatuses map[string]health.MmsDirectorStatus
expectedStep string
}
tests := map[string]TestConfig{
"No deadlock example should point to WaitFeatureCompatibilityVersionCorrect": {
processStatuses: noDeadlockHealthExample.MmsStatus,
expectedStep: "WaitFeatureCompatibilityVersionCorrect",
},
"Find single Started Step": {
processStatuses: map[string]health.MmsDirectorStatus{
"ignore": {
Plans: []*health.PlanStatus{
{
Moves: []*health.MoveStatus{
{
Steps: []*health.StepStatus{
{
Step: "will be ignored as completed",
Started: &tenMinutesAgo,
Completed: &now,
},
{
Step: "test",
Started: &tenMinutesAgo,
},
{
Step: "will be ignored as completed",
Started: &tenMinutesAgo,
Completed: &now,
},
},
},
},
Started: &tenMinutesAgo,
},
},
},
},
expectedStep: "test",
},
"Find no Step in completed plan": {
processStatuses: map[string]health.MmsDirectorStatus{
"ignore": {
Plans: []*health.PlanStatus{
{
Moves: []*health.MoveStatus{
{
Steps: []*health.StepStatus{
{
Step: "test",
Started: &tenMinutesAgo,
},
},
},
},
Started: &tenMinutesAgo,
Completed: &now,
},
},
},
},
expectedStep: "",
},
"Find single Started step in the latest plan only": {
processStatuses: map[string]health.MmsDirectorStatus{
"ignore": {
Plans: []*health.PlanStatus{
{
Moves: []*health.MoveStatus{
{
Steps: []*health.StepStatus{
{
Step: "will be ignored as only the last plan is evaluated",
Started: &tenMinutesAgo,
},
},
},
},
Started: &tenMinutesAgo,
},
{
Moves: []*health.MoveStatus{
{
Steps: []*health.StepStatus{
{
Step: "test",
Started: &tenMinutesAgo,
},
},
},
},
Started: &tenMinutesAgo,
},
},
},
},
expectedStep: "test",
},
}
for testName := range tests {
testConfig := tests[testName]
t.Run(testName, func(t *testing.T) {
step := findCurrentStep(testConfig.processStatuses)
if len(testConfig.expectedStep) == 0 {
assert.Nil(t, step)
} else {
assert.Equal(t, testConfig.expectedStep, step.Step)
}
})
}
}
// TestReadyWithWaitForCorrectBinaries tests the Static Containers Architecture mode for the Agent.
// In this case, the Readiness Probe needs to return Ready and let the StatefulSet Controller to proceed
// with the Pod rollout.
func TestReadyWithWaitForCorrectBinaries(t *testing.T) {
ctx := context.Background()
c := testConfigWithMongoUp("testdata/health-status-ok-with-WaitForCorrectBinaries.json", time.Second*30)
ready, err := isPodReady(ctx, c)
assert.True(t, ready)
assert.NoError(t, err)
}
// TestHeadlessAgentHasntReachedGoal verifies that the probe reports "false" if the config version is higher than the
// last achieved version of the Agent
// Note that the edge case is checked here: the health-status-ok.json has the "WaitRsInit" phase stuck in the last plan
// (as Agent doesn't marks all the step statuses finished when it reaches the goal) but this doesn't affect the result
// as the whole plan is complete already
func TestHeadlessAgentHasntReachedGoal(t *testing.T) {
ctx := context.Background()
t.Setenv(headlessAgent, "true")
c := testConfig("testdata/health-status-ok.json")
c.ClientSet = fake.NewSimpleClientset(testdata.TestPod(c.Namespace, c.Hostname), testdata.TestSecret(c.Namespace, c.AutomationConfigSecretName, 6))
ready, err := isPodReady(ctx, c)
assert.False(t, ready)
assert.NoError(t, err)
thePod, _ := c.ClientSet.CoreV1().Pods(c.Namespace).Get(ctx, c.Hostname, metav1.GetOptions{})
assert.Equal(t, map[string]string{"agent.mongodb.com/version": "5"}, thePod.Annotations)
}
// TestHeadlessAgentReachedGoal verifies that the probe reports "true" if the config version is equal to the
// last achieved version of the Agent
func TestHeadlessAgentReachedGoal(t *testing.T) {
ctx := context.Background()
t.Setenv(headlessAgent, "true")
c := testConfig("testdata/health-status-ok.json")
c.ClientSet = fake.NewSimpleClientset(testdata.TestPod(c.Namespace, c.Hostname), testdata.TestSecret(c.Namespace, c.AutomationConfigSecretName, 5))
ready, err := isPodReady(ctx, c)
assert.True(t, ready)
assert.NoError(t, err)
thePod, _ := c.ClientSet.CoreV1().Pods(c.Namespace).Get(ctx, c.Hostname, metav1.GetOptions{})
assert.Equal(t, map[string]string{"agent.mongodb.com/version": "5"}, thePod.Annotations)
}
func testConfig(healthFilePath string) config.Config {
return testConfigWithMongoUp(healthFilePath, 15*time.Second)
}
func testConfigWithMongoUp(healthFilePath string, timeSinceMongoLastUp time.Duration) config.Config {
file, err := os.Open(healthFilePath)
if err != nil {
panic(err)
}
defer file.Close()
status, err := parseHealthStatus(file)
if err != nil {
panic(err)
}
for key, processHealth := range status.Statuses {
processHealth.LastMongoUpTime = time.Now().Add(-timeSinceMongoLastUp).Unix()
// Need to reassign the object back to map as 'processHealth' is a copy of the struct
status.Statuses[key] = processHealth
}
return config.Config{
HealthStatusReader: NewTestHealthStatusReader(status),
Namespace: "test-ns",
AutomationConfigSecretName: "test-mongodb-automation-config",
Hostname: "test-mongodb-0",
}
}
func NewTestHealthStatusReader(status health.Status) io.Reader {
data, err := json.Marshal(status)
if err != nil {
panic(err)
}
return bytes.NewReader(data)
}