@@ -20,7 +20,6 @@ package postgrescluster
20
20
21
21
import (
22
22
"context"
23
- "io"
24
23
"os"
25
24
"strings"
26
25
"testing"
@@ -30,294 +29,16 @@ import (
30
29
"gotest.tools/v3/assert"
31
30
corev1 "k8s.io/api/core/v1"
32
31
apierrors "k8s.io/apimachinery/pkg/api/errors"
33
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34
- "k8s.io/apimachinery/pkg/labels"
35
- "k8s.io/apimachinery/pkg/util/sets"
36
32
"k8s.io/apimachinery/pkg/util/wait"
37
33
"k8s.io/client-go/tools/record"
38
34
"sigs.k8s.io/controller-runtime/pkg/client"
39
35
"sigs.k8s.io/controller-runtime/pkg/manager"
40
- "sigs.k8s.io/controller-runtime/pkg/reconcile"
41
36
"sigs.k8s.io/yaml"
42
37
43
- "github.com/crunchydata/postgres-operator/internal/patroni"
44
38
"github.com/crunchydata/postgres-operator/internal/testing/require"
45
39
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
46
40
)
47
41
48
- func TestReconcilerHandleDelete (t * testing.T ) {
49
- if ! strings .EqualFold (os .Getenv ("USE_EXISTING_CLUSTER" ), "true" ) {
50
- t .Skip ("requires a running garbage collection controller" )
51
- }
52
-
53
- ctx := context .Background ()
54
- env , cc := setupKubernetes (t )
55
- require .ParallelCapacity (t , 2 )
56
-
57
- ns := setupNamespace (t , cc )
58
- reconciler := Reconciler {
59
- Client : cc ,
60
- Owner : client .FieldOwner (t .Name ()),
61
- Recorder : new (record.FakeRecorder ),
62
- Tracer : otel .Tracer (t .Name ()),
63
- }
64
-
65
- var err error
66
- reconciler .PodExec , err = newPodExecutor (env .Config )
67
- assert .NilError (t , err )
68
-
69
- mustReconcile := func (t * testing.T , cluster * v1beta1.PostgresCluster ) reconcile.Result {
70
- t .Helper ()
71
- key := client .ObjectKeyFromObject (cluster )
72
- request := reconcile.Request {NamespacedName : key }
73
- result , err := reconciler .Reconcile (ctx , request )
74
- assert .NilError (t , err , "%+v" , err )
75
- return result
76
- }
77
-
78
- for _ , test := range []struct {
79
- name string
80
- beforeCreate func (* testing.T , * v1beta1.PostgresCluster )
81
- beforeDelete func (* testing.T , * v1beta1.PostgresCluster )
82
- propagation metav1.DeletionPropagation
83
-
84
- waitForRunningInstances int32
85
- }{
86
- // Normal delete of a healthly cluster.
87
- {
88
- name : "Background" , propagation : metav1 .DeletePropagationBackground ,
89
- waitForRunningInstances : 2 ,
90
- },
91
- // TODO(cbandy): metav1.DeletePropagationForeground
92
-
93
- // Normal delete of a healthy cluster after a failover.
94
- {
95
- name : "AfterFailover" , propagation : metav1 .DeletePropagationBackground ,
96
- waitForRunningInstances : 2 ,
97
-
98
- beforeDelete : func (t * testing.T , cluster * v1beta1.PostgresCluster ) {
99
- list := corev1.PodList {}
100
- selector , err := labels .Parse (strings .Join ([]string {
101
- "postgres-operator.crunchydata.com/cluster=" + cluster .Name ,
102
- "postgres-operator.crunchydata.com/instance" ,
103
- }, "," ))
104
- assert .NilError (t , err )
105
- assert .NilError (t , cc .List (ctx , & list ,
106
- client .InNamespace (cluster .Namespace ),
107
- client.MatchingLabelsSelector {Selector : selector }))
108
-
109
- var primary * corev1.Pod
110
- var replica * corev1.Pod
111
- for i := range list .Items {
112
- if list .Items [i ].Labels ["postgres-operator.crunchydata.com/role" ] == "replica" {
113
- replica = & list .Items [i ]
114
- } else {
115
- primary = & list .Items [i ]
116
- }
117
- }
118
-
119
- if true &&
120
- assert .Check (t , primary != nil , "expected to find a primary in %+v" , list .Items ) &&
121
- assert .Check (t , replica != nil , "expected to find a replica in %+v" , list .Items ) {
122
- success , err := patroni .Executor (
123
- func (_ context.Context , stdin io.Reader , stdout , stderr io.Writer , command ... string ) error {
124
- return reconciler .PodExec (replica .Namespace , replica .Name , "database" , stdin , stdout , stderr , command ... )
125
- },
126
- ).ChangePrimaryAndWait (ctx , primary .Name , replica .Name )
127
-
128
- assert .NilError (t , err )
129
- assert .Assert (t , success )
130
- }
131
- },
132
- },
133
-
134
- // Normal delete of cluster that could never run PostgreSQL.
135
- {
136
- name : "NeverRunning" , propagation : metav1 .DeletePropagationBackground ,
137
- waitForRunningInstances : 0 ,
138
-
139
- beforeCreate : func (_ * testing.T , cluster * v1beta1.PostgresCluster ) {
140
- cluster .Spec .Image = "example.com/does-not-exist"
141
- },
142
- },
143
- } {
144
- t .Run (test .name , func (t * testing.T ) {
145
- cluster := & v1beta1.PostgresCluster {}
146
- assert .NilError (t , yaml .Unmarshal ([]byte (`{
147
- spec: {
148
- postgresVersion: 13,
149
- instances: [
150
- {
151
- replicas: 2,
152
- dataVolumeClaimSpec: {
153
- accessModes: [ReadWriteOnce],
154
- resources: { requests: { storage: 1Gi } },
155
- },
156
- },
157
- ],
158
- backups: {
159
- pgbackrest: {
160
- repos: [{
161
- name: repo1,
162
- volume: {
163
- volumeClaimSpec: {
164
- accessModes: [ReadWriteOnce],
165
- resources: { requests: { storage: 1Gi } },
166
- },
167
- },
168
- }],
169
- },
170
- },
171
- },
172
- }` ), cluster ))
173
-
174
- cluster .Namespace = ns .Name
175
- cluster .Name = strings .ToLower (test .name )
176
- cluster .Spec .Image = CrunchyPostgresHAImage
177
- cluster .Spec .Backups .PGBackRest .Image = CrunchyPGBackRestImage
178
-
179
- if test .beforeCreate != nil {
180
- test .beforeCreate (t , cluster )
181
- }
182
-
183
- assert .NilError (t , cc .Create (ctx , cluster ))
184
-
185
- t .Cleanup (func () {
186
- // Remove finalizers, if any, so the namespace can terminate.
187
- assert .Check (t , client .IgnoreNotFound (
188
- cc .Patch (ctx , cluster , client .RawPatch (
189
- client .Merge .Type (), []byte (`{"metadata":{"finalizers":[]}}` )))))
190
- })
191
-
192
- // Start cluster.
193
- mustReconcile (t , cluster )
194
-
195
- assert .NilError (t ,
196
- cc .Get (ctx , client .ObjectKeyFromObject (cluster ), cluster ))
197
- assert .Assert (t ,
198
- sets .NewString (cluster .Finalizers ... ).
199
- Has ("postgres-operator.crunchydata.com/finalizer" ),
200
- "cluster should immediately have a finalizer" )
201
-
202
- // Continue until instances are healthy.
203
- if ready := int32 (0 ); ! assert .Check (t ,
204
- wait .Poll (time .Second , Scale (time .Minute ), func () (bool , error ) {
205
- mustReconcile (t , cluster )
206
- assert .NilError (t , cc .Get (ctx , client .ObjectKeyFromObject (cluster ), cluster ))
207
-
208
- ready = 0
209
- for _ , set := range cluster .Status .InstanceSets {
210
- ready += set .ReadyReplicas
211
- }
212
- return ready >= test .waitForRunningInstances , nil
213
- }), "expected %v instances to be ready, got: %v" , test .waitForRunningInstances , ready ,
214
- ) {
215
- t .FailNow ()
216
- }
217
-
218
- if test .beforeDelete != nil {
219
- test .beforeDelete (t , cluster )
220
- }
221
-
222
- switch test .propagation {
223
- case metav1 .DeletePropagationBackground :
224
- // Background deletion is the default for custom resources.
225
- // - https://issue.k8s.io/81628
226
- assert .NilError (t , cc .Delete (ctx , cluster ))
227
- default :
228
- assert .NilError (t , cc .Delete (ctx , cluster ,
229
- client .PropagationPolicy (test .propagation )))
230
- }
231
-
232
- // Stop cluster.
233
- result := mustReconcile (t , cluster )
234
-
235
- // If things started running, then they should stop in a certain order.
236
- if test .waitForRunningInstances > 0 {
237
-
238
- // Replicas should stop first, leaving just the one primary.
239
- var instances []corev1.Pod
240
- assert .NilError (t , wait .Poll (time .Second , Scale (time .Minute ), func () (bool , error ) {
241
- if result .Requeue {
242
- result = mustReconcile (t , cluster )
243
- }
244
-
245
- list := corev1.PodList {}
246
- selector , err := labels .Parse (strings .Join ([]string {
247
- "postgres-operator.crunchydata.com/cluster=" + cluster .Name ,
248
- "postgres-operator.crunchydata.com/instance" ,
249
- }, "," ))
250
- assert .NilError (t , err )
251
- assert .NilError (t , cc .List (ctx , & list ,
252
- client .InNamespace (cluster .Namespace ),
253
- client.MatchingLabelsSelector {Selector : selector }))
254
-
255
- instances = list .Items
256
-
257
- // Patroni doesn't use "primary" to identify the primary.
258
- return len (instances ) == 1 &&
259
- instances [0 ].Labels ["postgres-operator.crunchydata.com/role" ] == "master" , nil
260
- }), "expected one instance, got:\n %+v" , instances )
261
-
262
- // Patroni DCS objects should not be deleted yet.
263
- {
264
- list := corev1.EndpointsList {}
265
- selector , err := labels .Parse (strings .Join ([]string {
266
- "postgres-operator.crunchydata.com/cluster=" + cluster .Name ,
267
- "postgres-operator.crunchydata.com/patroni" ,
268
- }, "," ))
269
- assert .NilError (t , err )
270
- assert .NilError (t , cc .List (ctx , & list ,
271
- client .InNamespace (cluster .Namespace ),
272
- client.MatchingLabelsSelector {Selector : selector }))
273
-
274
- assert .Assert (t , len (list .Items ) >= 2 , // config + leader
275
- "expected Patroni DCS objects to remain, there are %v" ,
276
- len (list .Items ))
277
-
278
- // Endpoints are deleted differently than other resources, and
279
- // Patroni might have recreated them to stay alive. Check that
280
- // they are all from before the cluster delete operation.
281
- // - https://issue.k8s.io/99407
282
- assert .NilError (t ,
283
- cc .Get (ctx , client .ObjectKeyFromObject (cluster ), cluster ))
284
-
285
- for _ , endpoints := range list .Items {
286
- assert .Assert (t ,
287
- endpoints .CreationTimestamp .Time .Before (cluster .DeletionTimestamp .Time ),
288
- `expected %q to be after %+v` , cluster .DeletionTimestamp , endpoints )
289
- }
290
- }
291
- }
292
-
293
- // Continue until cluster is gone.
294
- assert .NilError (t , wait .Poll (time .Second , Scale (time .Minute ), func () (bool , error ) {
295
- mustReconcile (t , cluster )
296
-
297
- err := cc .Get (ctx , client .ObjectKeyFromObject (cluster ), cluster )
298
- return apierrors .IsNotFound (err ), client .IgnoreNotFound (err )
299
- }), "expected cluster to be deleted, got:\n %+v" , * cluster )
300
-
301
- var endpoints []corev1.Endpoints
302
- assert .NilError (t , wait .Poll (time .Second , Scale (time .Minute / 3 ), func () (bool , error ) {
303
- list := corev1.EndpointsList {}
304
- selector , err := labels .Parse (strings .Join ([]string {
305
- "postgres-operator.crunchydata.com/cluster=" + cluster .Name ,
306
- "postgres-operator.crunchydata.com/patroni" ,
307
- }, "," ))
308
- assert .NilError (t , err )
309
- assert .NilError (t , cc .List (ctx , & list ,
310
- client .InNamespace (cluster .Namespace ),
311
- client.MatchingLabelsSelector {Selector : selector }))
312
-
313
- endpoints = list .Items
314
-
315
- return len (endpoints ) == 0 , nil
316
- }), "Patroni DCS objects should be gone, got:\n %+v" , endpoints )
317
- })
318
- }
319
- }
320
-
321
42
func TestReconcilerHandleDeleteNamespace (t * testing.T ) {
322
43
if ! strings .EqualFold (os .Getenv ("USE_EXISTING_CLUSTER" ), "true" ) {
323
44
t .Skip ("requires a running garbage collection controller" )
0 commit comments