Skip to content

Commit 7bf9511

Browse files
wadlejitendradanishedbarmruhh24klitaocdl
authored
test: adds E2E tests for the cnpg plugin hibernate command. (cloudnative-pg#855)
Closes cloudnative-pg#941 Signed-off-by: Jitendra Wadle <jitendra.wadle@enterprisedb.com> Signed-off-by: Danish Khan <danish.khan@enterprisedb.com> Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com> Signed-off-by: Hai He <hai.he@enterprisedb.com> Signed-off-by: Tao Li <tao.li@enterprisedb.com> Co-authored-by: Danish Khan <danish.khan@enterprisedb.com> Co-authored-by: Armando Ruocco <armando.ruocco@enterprisedb.com> Co-authored-by: Hai He <hai.he@enterprisedb.com> Co-authored-by: Tao Li <tao.li@enterprisedb.com>
1 parent 8b45786 commit 7bf9511

File tree

3 files changed

+417
-0
lines changed

3 files changed

+417
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
apiVersion: postgresql.cnpg.io/v1
2+
kind: Cluster
3+
metadata:
4+
name: postgresql-hibernate
5+
spec:
6+
instances: 3
7+
8+
postgresql:
9+
parameters:
10+
log_checkpoints: "on"
11+
log_lock_waits: "on"
12+
log_min_duration_statement: '1000'
13+
log_statement: 'ddl'
14+
log_temp_files: '1024'
15+
log_autovacuum_min_duration: '1s'
16+
log_replication_commands: 'on'
17+
18+
# Example of rolling update strategy:
19+
# - unsupervised: automated update of the primary once all
20+
# replicas have been upgraded (default)
21+
# - supervised: requires manual supervision to perform
22+
# the switchover of the primary
23+
primaryUpdateStrategy: unsupervised
24+
25+
bootstrap:
26+
initdb:
27+
database: app
28+
owner: app
29+
30+
# Persistent storage configuration
31+
storage:
32+
storageClass: ${E2E_DEFAULT_STORAGE_CLASS}
33+
size: 1Gi

tests/e2e/hibernation_test.go

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
package e2e
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
8+
corev1 "k8s.io/api/core/v1"
9+
v1 "k8s.io/api/rbac/v1"
10+
"k8s.io/apimachinery/pkg/types"
11+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
12+
13+
apiv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
14+
"github.com/cloudnative-pg/cloudnative-pg/pkg/specs"
15+
"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
16+
"github.com/cloudnative-pg/cloudnative-pg/tests"
17+
testsUtils "github.com/cloudnative-pg/cloudnative-pg/tests/utils"
18+
19+
. "github.com/onsi/ginkgo/v2"
20+
. "github.com/onsi/gomega"
21+
)
22+
23+
var _ = Describe("Cluster Hibernation with plugin", func() {
24+
type mode string
25+
type hibernateSatusMessage string
26+
type expectedKeysInStatus string
27+
const (
28+
sampleFileClusterWithPGWalVolume = fixturesDir + "/base/cluster-storage-class.yaml.template"
29+
sampleFileClusterWithOutPGWalVolume = fixturesDir + "/hibernate/" +
30+
"cluster-storage-class-without-wal.yaml.template"
31+
level = tests.Medium
32+
HibernateOn mode = "on"
33+
HibernateOff mode = "off"
34+
HibernateStatus mode = "status"
35+
clusterOffStatusMessage hibernateSatusMessage = "No Hibernation. Cluster Deployed."
36+
clusterOnStatusMessage hibernateSatusMessage = "Cluster Hibernated"
37+
summaryInStatus expectedKeysInStatus = "summary"
38+
tableName = "test"
39+
)
40+
var namespace string
41+
BeforeEach(func() {
42+
if testLevelEnv.Depth < int(level) {
43+
Skip("Test depth is lower than the amount requested for this test")
44+
}
45+
})
46+
47+
JustAfterEach(func() {
48+
if CurrentSpecReport().Failed() {
49+
env.DumpNamespaceObjects(namespace, "out/"+CurrentSpecReport().FullText()+".log")
50+
}
51+
})
52+
53+
AfterEach(func() {
54+
err := env.DeleteNamespace(namespace)
55+
Expect(err).ToNot(HaveOccurred())
56+
})
57+
58+
Context("hibernate", func() {
59+
var err error
60+
getPrimaryAndClusterManifest := func(namespace, clusterName string) ([]byte, *apiv1.Cluster, string) {
61+
var beforeHibernationClusterInfo *apiv1.Cluster
62+
var clusterManifest []byte
63+
var beforeHibernationCurrentPrimary string
64+
By("collecting current primary details", func() {
65+
beforeHibernationClusterInfo, err = env.GetCluster(namespace, clusterName)
66+
Expect(err).ToNot(HaveOccurred())
67+
beforeHibernationCurrentPrimary = beforeHibernationClusterInfo.Status.CurrentPrimary
68+
// collect expected cluster manifesto info
69+
clusterManifest, err = json.Marshal(&beforeHibernationClusterInfo)
70+
Expect(err).ToNot(HaveOccurred())
71+
})
72+
return clusterManifest, beforeHibernationClusterInfo, beforeHibernationCurrentPrimary
73+
}
74+
getPvc := func(role utils.PVCRole, clusterInfo *apiv1.Cluster, instanceName string) corev1.PersistentVolumeClaim {
75+
pvcName := specs.GetPVCName(*clusterInfo, instanceName, role)
76+
pvcInfo := corev1.PersistentVolumeClaim{}
77+
err = testsUtils.GetObject(env, ctrlclient.ObjectKey{Namespace: namespace, Name: pvcName}, &pvcInfo)
78+
Expect(err).ToNot(HaveOccurred())
79+
return pvcInfo
80+
}
81+
performHibernation := func(mode mode, namespace, clusterName string) {
82+
By(fmt.Sprintf("performing hibernation %v", mode), func() {
83+
_, _, err := testsUtils.Run(fmt.Sprintf("kubectl cnpg hibernate %v %v -n %v",
84+
mode, clusterName, namespace))
85+
Expect(err).ToNot(HaveOccurred())
86+
})
87+
By(fmt.Sprintf("verifying cluster %v pods are removed", clusterName), func() {
88+
Eventually(func(g Gomega) {
89+
podList, _ := env.GetClusterPodList(namespace, clusterName)
90+
g.Expect(len(podList.Items)).Should(BeEquivalentTo(0))
91+
}, 300).Should(Succeed())
92+
})
93+
}
94+
95+
getHibernationStatusInJSON := func(namespace, clusterName string) map[string]interface{} {
96+
var data map[string]interface{}
97+
By("getting hibernation status", func() {
98+
stdOut, _, err := testsUtils.Run(fmt.Sprintf("kubectl cnpg hibernate %v %v -n %v -ojson",
99+
HibernateStatus, clusterName, namespace))
100+
Expect(err).ToNot(HaveOccurred(), stdOut)
101+
err = json.Unmarshal([]byte(stdOut), &data)
102+
Expect(err).ToNot(HaveOccurred())
103+
})
104+
return data
105+
}
106+
107+
verifySummaryInHibernationStatus := func(clusterName string, message hibernateSatusMessage) {
108+
statusOut := getHibernationStatusInJSON(namespace, clusterName)
109+
actualStatus := statusOut[string(summaryInStatus)].(map[string]interface{})["status"].(string)
110+
Expect(strings.Contains(string(message), actualStatus)).Should(BeEquivalentTo(true), statusOut)
111+
}
112+
verifyClusterResources := func(namespace, clusterName string, roles []utils.PVCRole) {
113+
By(fmt.Sprintf("verifying cluster resources are removed "+
114+
"post hibernation where roles %v", roles), func() {
115+
timeout := 120
116+
117+
By(fmt.Sprintf("verifying cluster %v is removed", clusterName), func() {
118+
Eventually(func() (bool, apiv1.Cluster) {
119+
cluster := &apiv1.Cluster{}
120+
err := env.Client.Get(env.Ctx,
121+
ctrlclient.ObjectKey{Namespace: namespace, Name: clusterName},
122+
cluster)
123+
if err != nil {
124+
return true, apiv1.Cluster{}
125+
}
126+
return false, *cluster
127+
}, timeout).Should(BeTrue())
128+
})
129+
130+
By(fmt.Sprintf("verifying cluster %v PVCs are removed", clusterName), func() {
131+
Eventually(func() (int, error) {
132+
pvcList, err := env.GetPVCList(namespace)
133+
if err != nil {
134+
return -1, err
135+
}
136+
return len(pvcList.Items), nil
137+
}, timeout).Should(BeEquivalentTo(len(roles)))
138+
})
139+
140+
By(fmt.Sprintf("verifying cluster %v configMap is removed", clusterName), func() {
141+
Eventually(func() (bool, corev1.ConfigMap) {
142+
configMap := corev1.ConfigMap{}
143+
err = env.Client.Get(env.Ctx,
144+
ctrlclient.ObjectKey{Namespace: namespace, Name: apiv1.DefaultMonitoringConfigMapName},
145+
&configMap)
146+
if err != nil {
147+
return true, corev1.ConfigMap{}
148+
}
149+
return false, configMap
150+
}, timeout).Should(BeTrue())
151+
})
152+
153+
By(fmt.Sprintf("verifying cluster %v secrets are removed", clusterName), func() {
154+
Eventually(func() (bool, corev1.SecretList, error) {
155+
secretList := corev1.SecretList{}
156+
err = env.Client.List(env.Ctx, &secretList, ctrlclient.InNamespace(namespace))
157+
if err != nil {
158+
return false, corev1.SecretList{}, err
159+
}
160+
var getClusterSecrets []string
161+
for _, secret := range secretList.Items {
162+
if strings.HasPrefix(secret.GetName(), clusterName) {
163+
getClusterSecrets = append(getClusterSecrets, secret.GetName())
164+
}
165+
}
166+
if len(getClusterSecrets) == 0 {
167+
return true, corev1.SecretList{}, nil
168+
}
169+
return false, secretList, nil
170+
}, timeout).Should(BeTrue())
171+
})
172+
173+
By(fmt.Sprintf("verifying cluster %v role is removed", clusterName), func() {
174+
Eventually(func() (bool, v1.Role) {
175+
role := v1.Role{}
176+
err = env.Client.Get(env.Ctx,
177+
ctrlclient.ObjectKey{Namespace: namespace, Name: clusterName},
178+
&role)
179+
if err != nil {
180+
return true, v1.Role{}
181+
}
182+
return false, role
183+
}, timeout).Should(BeTrue())
184+
})
185+
186+
By(fmt.Sprintf("verifying cluster %v rolebinding is removed", clusterName), func() {
187+
Eventually(func() (bool, v1.RoleBinding) {
188+
roleBinding := v1.RoleBinding{}
189+
err = env.Client.Get(env.Ctx,
190+
ctrlclient.ObjectKey{Namespace: namespace, Name: clusterName},
191+
&roleBinding)
192+
if err != nil {
193+
return true, v1.RoleBinding{}
194+
}
195+
return false, roleBinding
196+
}, timeout).Should(BeTrue())
197+
})
198+
})
199+
}
200+
verifyPvc := func(role utils.PVCRole, pvcUid types.UID, clusterInfo *apiv1.Cluster, clusterManifest []byte,
201+
instanceName string,
202+
) {
203+
pvcInfo := getPvc(role, clusterInfo, instanceName)
204+
Expect(pvcUid).Should(BeEquivalentTo(pvcInfo.GetUID()))
205+
// pvc should be attached annotation with pgControlData and Cluster manifesto
206+
expectedAnnotationKeyPresent := []string{
207+
utils.HibernatePgControlDataAnnotationName,
208+
utils.HibernateClusterManifestAnnotationName,
209+
}
210+
testsUtils.ObjectHasAnnotations(&pvcInfo, expectedAnnotationKeyPresent)
211+
expectedAnnotation := map[string]string{
212+
utils.HibernateClusterManifestAnnotationName: string(clusterManifest),
213+
}
214+
testsUtils.ObjectMatchesAnnotations(&pvcInfo, expectedAnnotation)
215+
}
216+
217+
assertHibernation := func(namespace, clusterName, tableName string) {
218+
var beforeHibernationPgWalPvcUID types.UID
219+
var beforeHibernationPgDataPvcUID types.UID
220+
221+
// Write a table and some data on the "app" database
222+
AssertCreateTestData(namespace, clusterName, tableName)
223+
clusterManifest, beforeHibernationClusterInfo, currentPrimary := getPrimaryAndClusterManifest(namespace, clusterName)
224+
225+
By("collecting pgWal pvc details of current primary", func() {
226+
pvcInfo := getPvc(utils.PVCRolePgWal, beforeHibernationClusterInfo, currentPrimary)
227+
beforeHibernationPgWalPvcUID = pvcInfo.GetUID()
228+
})
229+
230+
By("collecting pgData pvc details of current primary", func() {
231+
pvcInfo := getPvc(utils.PVCRolePgData, beforeHibernationClusterInfo, currentPrimary)
232+
beforeHibernationPgDataPvcUID = pvcInfo.GetUID()
233+
})
234+
235+
By(fmt.Sprintf("verifying hibernation status"+
236+
" before hibernate on cluster %v", clusterName), func() {
237+
verifySummaryInHibernationStatus(clusterName, clusterOffStatusMessage)
238+
})
239+
240+
performHibernation(HibernateOn, namespace, clusterName)
241+
242+
By(fmt.Sprintf("verifying hibernation status"+
243+
" after hibernate on cluster %v", clusterName), func() {
244+
verifySummaryInHibernationStatus(clusterName, clusterOnStatusMessage)
245+
})
246+
247+
// After hibernation, it will destroy all the resources generated by the cluster,
248+
// except the PVCs that belong to the PostgreSQL primary instance.
249+
verifyClusterResources(namespace, clusterName, []utils.PVCRole{utils.PVCRolePgWal, utils.PVCRolePgData})
250+
251+
By("verifying primary pgWal pvc info", func() {
252+
verifyPvc(utils.PVCRolePgWal, beforeHibernationPgWalPvcUID, beforeHibernationClusterInfo,
253+
clusterManifest, currentPrimary)
254+
})
255+
256+
By("verifying primary pgData pvc info", func() {
257+
verifyPvc(utils.PVCRolePgData, beforeHibernationPgDataPvcUID, beforeHibernationClusterInfo,
258+
clusterManifest, currentPrimary)
259+
})
260+
261+
// verifying hibernation off
262+
performHibernation(HibernateOff, namespace, clusterName)
263+
264+
By(fmt.Sprintf("verifying hibernation status after "+
265+
"perform hibernation off on cluster %v", clusterName), func() {
266+
verifySummaryInHibernationStatus(clusterName, clusterOffStatusMessage)
267+
})
268+
269+
AssertClusterIsReady(namespace, clusterName, 600, env)
270+
// Test data should be present after hibernation off
271+
AssertDataExpectedCount(namespace, currentPrimary, tableName, 2)
272+
}
273+
274+
When("cluster setup with PG-WAL volume", func() {
275+
It("hibernation process should work", func() {
276+
namespace = "hibernation-on-with-pg-wal"
277+
clusterName, err := env.GetResourceNameFromYAML(sampleFileClusterWithPGWalVolume)
278+
Expect(err).ToNot(HaveOccurred())
279+
// Create a cluster in a namespace we'll delete after the test
280+
err = env.CreateNamespace(namespace)
281+
Expect(err).ToNot(HaveOccurred())
282+
AssertCreateCluster(namespace, clusterName, sampleFileClusterWithPGWalVolume, env)
283+
assertHibernation(namespace, clusterName, tableName)
284+
})
285+
})
286+
When("cluster setup without PG-WAL volume", func() {
287+
It("hibernation process should work", func() {
288+
var beforeHibernationPgDataPvcUID types.UID
289+
290+
namespace = "hibernation-without-pg-wal"
291+
clusterName, err := env.GetResourceNameFromYAML(sampleFileClusterWithOutPGWalVolume)
292+
Expect(err).ToNot(HaveOccurred())
293+
// Create a cluster in a namespace we'll delete after the test
294+
err = env.CreateNamespace(namespace)
295+
Expect(err).ToNot(HaveOccurred())
296+
AssertCreateCluster(namespace, clusterName, sampleFileClusterWithOutPGWalVolume, env)
297+
// Write a table and some data on the "app" database
298+
AssertCreateTestData(namespace, clusterName, tableName)
299+
clusterManifest, beforeHibernationClusterInfo, currentPrimary := getPrimaryAndClusterManifest(namespace,
300+
clusterName)
301+
302+
By("collecting pgData pvc details of current primary", func() {
303+
pvcInfo := getPvc(utils.PVCRolePgData, beforeHibernationClusterInfo, currentPrimary)
304+
beforeHibernationPgDataPvcUID = pvcInfo.GetUID()
305+
})
306+
307+
By(fmt.Sprintf("verifying hibernation status"+
308+
" before hibernate on cluster %v", clusterName), func() {
309+
verifySummaryInHibernationStatus(clusterName, clusterOffStatusMessage)
310+
})
311+
312+
performHibernation(HibernateOn, namespace, clusterName)
313+
314+
By(fmt.Sprintf("verifying hibernation status"+
315+
" after hibernate on cluster %v", clusterName), func() {
316+
verifySummaryInHibernationStatus(clusterName, clusterOnStatusMessage)
317+
})
318+
319+
// After hibernation, it will destroy all the resources generated by the cluster,
320+
// except the PVCs that belong to the PostgreSQL primary instance.
321+
verifyClusterResources(namespace, clusterName, []utils.PVCRole{utils.PVCRolePgData})
322+
323+
By("verifying primary pgData pvc info", func() {
324+
verifyPvc(utils.PVCRolePgData, beforeHibernationPgDataPvcUID, beforeHibernationClusterInfo,
325+
clusterManifest, currentPrimary)
326+
})
327+
328+
// verifying hibernation off
329+
performHibernation(HibernateOff, namespace, clusterName)
330+
By(fmt.Sprintf("verifying hibernation status"+
331+
" before hibernate on cluster %v", clusterName), func() {
332+
verifySummaryInHibernationStatus(clusterName, clusterOffStatusMessage)
333+
})
334+
335+
AssertClusterIsReady(namespace, clusterName, 600, env)
336+
// Test data should be present after hibernation off
337+
AssertDataExpectedCount(namespace, currentPrimary, tableName, 2)
338+
})
339+
})
340+
When("cluster hibernation after switchover", func() {
341+
It("hibernation process should work", func() {
342+
namespace = "hibernation-with-switchover"
343+
clusterName, err := env.GetResourceNameFromYAML(sampleFileClusterWithPGWalVolume)
344+
Expect(err).ToNot(HaveOccurred())
345+
// Create a cluster in a namespace we'll delete after the test
346+
err = env.CreateNamespace(namespace)
347+
Expect(err).ToNot(HaveOccurred())
348+
AssertCreateCluster(namespace, clusterName, sampleFileClusterWithPGWalVolume, env)
349+
AssertSwitchover(namespace, clusterName, env)
350+
assertHibernation(namespace, clusterName, tableName)
351+
})
352+
})
353+
})
354+
})

0 commit comments

Comments
 (0)