Skip to content

Commit c13154e

Browse files
authored
Replace TestReconcilerHandleDelete With Kuttl Test (#3169)
TestReconcilerHandleDelete was prone to flakes when run with `envtest-existing`, and so is here replaced by a KUTTL test with matching functionality. Issue [sc-14264]
1 parent 44c4c5c commit c13154e

17 files changed

+331
-280
lines changed

internal/controller/postgrescluster/delete_test.go

Lines changed: 0 additions & 279 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ package postgrescluster
2020

2121
import (
2222
"context"
23-
"io"
2423
"os"
2524
"strings"
2625
"testing"
@@ -30,294 +29,16 @@ import (
3029
"gotest.tools/v3/assert"
3130
corev1 "k8s.io/api/core/v1"
3231
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"
3632
"k8s.io/apimachinery/pkg/util/wait"
3733
"k8s.io/client-go/tools/record"
3834
"sigs.k8s.io/controller-runtime/pkg/client"
3935
"sigs.k8s.io/controller-runtime/pkg/manager"
40-
"sigs.k8s.io/controller-runtime/pkg/reconcile"
4136
"sigs.k8s.io/yaml"
4237

43-
"github.com/crunchydata/postgres-operator/internal/patroni"
4438
"github.com/crunchydata/postgres-operator/internal/testing/require"
4539
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
4640
)
4741

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-
32142
func TestReconcilerHandleDeleteNamespace(t *testing.T) {
32243
if !strings.EqualFold(os.Getenv("USE_EXISTING_CLUSTER"), "true") {
32344
t.Skip("requires a running garbage collection controller")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
apiVersion: postgres-operator.crunchydata.com/v1beta1
3+
kind: PostgresCluster
4+
metadata:
5+
name: delete
6+
spec:
7+
postgresVersion: ${KUTTL_PG_VERSION}
8+
instances:
9+
- name: instance1
10+
replicas: 1
11+
dataVolumeClaimSpec:
12+
accessModes:
13+
- "ReadWriteOnce"
14+
resources:
15+
requests:
16+
storage: 1Gi
17+
backups:
18+
pgbackrest:
19+
repos:
20+
- name: repo1
21+
volume:
22+
volumeClaimSpec:
23+
accessModes:
24+
- "ReadWriteOnce"
25+
resources:
26+
requests:
27+
storage: 1Gi
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
apiVersion: postgres-operator.crunchydata.com/v1beta1
3+
kind: PostgresCluster
4+
metadata:
5+
name: delete
6+
status:
7+
instances:
8+
- name: instance1
9+
readyReplicas: 1
10+
replicas: 1
11+
updatedReplicas: 1
12+
---
13+
apiVersion: batch/v1
14+
kind: Job
15+
metadata:
16+
labels:
17+
postgres-operator.crunchydata.com/cluster: delete
18+
postgres-operator.crunchydata.com/pgbackrest-backup: replica-create
19+
status:
20+
succeeded: 1
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
# Remove the cluster.
3+
apiVersion: kuttl.dev/v1beta1
4+
kind: TestStep
5+
delete:
6+
- apiVersion: postgres-operator.crunchydata.com/v1beta1
7+
kind: PostgresCluster
8+
name: delete
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
apiVersion: postgres-operator.crunchydata.com/v1beta1
3+
kind: PostgresCluster
4+
labels:
5+
postgres-operator.crunchydata.com/cluster: delete
6+
---
7+
apiVersion: apps/v1
8+
kind: StatefulSet
9+
labels:
10+
postgres-operator.crunchydata.com/cluster: delete
11+
---
12+
apiVersion: v1
13+
kind: Pod
14+
labels:
15+
postgres-operator.crunchydata.com/cluster: delete
16+
---
17+
apiVersion: v1
18+
kind: Service
19+
labels:
20+
postgres-operator.crunchydata.com/cluster: delete
21+
---
22+
apiVersion: v1
23+
kind: Secret
24+
labels:
25+
postgres-operator.crunchydata.com/cluster: delete

0 commit comments

Comments
 (0)