Skip to content

Commit 3d42815

Browse files
committed
test: singlek8s: improve test for multi-controller env
Signed-off-by: Yuji Ito <llamerada.jp@gmail.com>
1 parent 9714974 commit 3d42815

2 files changed

Lines changed: 206 additions & 22 deletions

File tree

test/e2e/singlek8s/multi_rook_ceph_test.go

Lines changed: 191 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package singlek8s
22

33
import (
44
"fmt"
5+
"strings"
56

7+
mantlev1 "github.com/cybozu-go/mantle/api/v1"
8+
"github.com/cybozu-go/mantle/internal/controller"
69
"github.com/cybozu-go/mantle/test/util"
710
. "github.com/onsi/ginkgo/v2"
811
. "github.com/onsi/gomega"
12+
corev1 "k8s.io/api/core/v1"
913
)
1014

1115
type multiRookCephTest struct {
@@ -96,9 +100,26 @@ func (test *multiRookCephTest) testMain() {
96100
var srcImageName1, srcImageName2, srcImageNameDummy string
97101
var expectImageAndSnaps1, expectImageAndSnaps2 []string
98102
var dummySnapInfo, dummyCloneInfo *rbdInfo
103+
var backup1, backup2 *mantlev1.MantleBackup
99104

100-
It("create PVC, backup, and restore in the tenant namespace 1", func() {
101-
test.createPVCBackupRestoreWithData(test.tenantNamespace1, test.storageClassName1, testData1)
105+
// --- Cluster 1 backup lifecycle ---
106+
107+
It("create PVC and write data in tenant namespace 1", func() {
108+
err := applyPVCTemplate(test.tenantNamespace1, test.pvcName, test.storageClassName1)
109+
Expect(err).NotTo(HaveOccurred())
110+
err = writeTestData(test.tenantNamespace1, test.pvcName, testData1)
111+
Expect(err).NotTo(HaveOccurred())
112+
})
113+
114+
It("create backup in tenant namespace 1 and wait for snapshot", func() {
115+
err := applyMantleBackupTemplate(test.tenantNamespace1, test.pvcName, test.mantleBackupName)
116+
Expect(err).NotTo(HaveOccurred())
117+
118+
Eventually(func(g Gomega) {
119+
ok, err := isMantleBackupSnapshotCaptured(test.tenantNamespace1, test.mantleBackupName)
120+
g.Expect(err).NotTo(HaveOccurred())
121+
g.Expect(ok).To(BeTrue())
122+
}).Should(Succeed())
102123

103124
pvName, err := getPVFromPVC(test.tenantNamespace1, test.pvcName)
104125
Expect(err).NotTo(HaveOccurred())
@@ -111,6 +132,86 @@ func (test *multiRookCephTest) testMain() {
111132
}
112133
})
113134

135+
It("check labels on MantleBackup after backup in tenant namespace 1", func() {
136+
pvc, err := getObject[corev1.PersistentVolumeClaim]("pvc", test.tenantNamespace1, test.pvcName)
137+
Expect(err).NotTo(HaveOccurred())
138+
139+
mb, err := getMB(test.tenantNamespace1, test.mantleBackupName)
140+
Expect(err).NotTo(HaveOccurred())
141+
142+
mantleLabels := make(map[string]string)
143+
for k, v := range mb.GetLabels() {
144+
if strings.HasPrefix(k, "mantle.cybozu.io/") {
145+
mantleLabels[k] = v
146+
}
147+
}
148+
Expect(mantleLabels).To(Equal(map[string]string{
149+
"mantle.cybozu.io/cluster-id": cephCluster1Namespace,
150+
"mantle.cybozu.io/local-backup-target-pvc-uid": string(pvc.GetUID()),
151+
}))
152+
})
153+
154+
It("verify for tenant namespace 1 runs only in ceph cluster 1, is cleaned up, and never appears in ceph cluster 2", func() {
155+
// The MantleBackup already exists, so the verify resource names (derived from its
156+
// UID) are known now, before verify runs. This lets us start monitoring ceph cluster 2
157+
// from the beginning of the verify lifecycle.
158+
mb, err := getMB(test.tenantNamespace1, test.mantleBackupName)
159+
Expect(err).NotTo(HaveOccurred())
160+
backup1 = mb
161+
162+
jobName := controller.MakeVerifyJobName(backup1)
163+
pvcName := controller.MakeVerifyPVCName(backup1)
164+
165+
// Continuously monitor until ceph cluster 1 finishes verify and cleans up its
166+
// resources. On every poll, hard-assert (via the outer Expect, not g.Expect) that
167+
// the verify resources never appear in ceph cluster 2, so even a transient
168+
// appearance fails the test immediately instead of being tolerated as a retryable
169+
// condition. A single point-in-time check would miss resources that are created
170+
// briefly in the wrong namespace and then cleaned up quickly.
171+
Eventually(func(g Gomega) {
172+
// The verify resources must NEVER appear in ceph cluster 2 at any point, so it doesn't use 'g'.
173+
existsJob2, err := checkJobExists(cephCluster2Namespace, jobName)
174+
Expect(err).NotTo(HaveOccurred())
175+
Expect(existsJob2).To(BeFalse(), "verify job must not run in ceph cluster 2")
176+
existsPVC2, err := checkPVCExists(cephCluster2Namespace, pvcName)
177+
Expect(err).NotTo(HaveOccurred())
178+
Expect(existsPVC2).To(BeFalse(), "verify PVC must not be created in ceph cluster 2")
179+
180+
mb, err := getMB(test.tenantNamespace1, test.mantleBackupName)
181+
g.Expect(err).NotTo(HaveOccurred())
182+
// If the ceph cluster 2 controller mistakenly runs verify for this backup,
183+
// the verify job fails because the RBD snapshot does not exist in ceph cluster 2,
184+
// which causes IsVerifiedFalse to be set instead of IsVerifiedTrue.
185+
Expect(mb.IsVerifiedFalse()).To(BeFalse(),
186+
"verify failed: ceph cluster 2 controller may have run verify for ceph cluster 1's backup")
187+
188+
// Terminating condition: verify succeeded in ceph cluster 1 and its resources
189+
// have been cleaned up.
190+
g.Expect(mb.IsVerifiedTrue()).To(BeTrue())
191+
existsJob1, err := checkJobExists(cephCluster1Namespace, jobName)
192+
g.Expect(err).NotTo(HaveOccurred())
193+
g.Expect(existsJob1).To(BeFalse(), "verify job should be cleaned up from ceph cluster 1")
194+
existsPVC1, err := checkPVCExists(cephCluster1Namespace, pvcName)
195+
g.Expect(err).NotTo(HaveOccurred())
196+
g.Expect(existsPVC1).To(BeFalse(), "verify PVC should be cleaned up from ceph cluster 1")
197+
}).Should(Succeed())
198+
})
199+
200+
It("create restore in tenant namespace 1 and wait for ready", func() {
201+
err := applyMantleRestoreTemplate(test.tenantNamespace1, test.mantleRestoreName, test.mantleBackupName)
202+
Expect(err).NotTo(HaveOccurred())
203+
Eventually(func() bool {
204+
return isMantleRestoreReady(test.tenantNamespace1, test.mantleRestoreName)
205+
}).Should(BeTrue())
206+
})
207+
208+
// --- Dummy data ---
209+
// Manually create RBD images/snaps/clones in ceph cluster 2 using the same names as
210+
// those created by ceph cluster 1's controller. This lets the subsequent RBD state
211+
// checks verify that ceph cluster 1's controller never touches ceph cluster 2's RBD
212+
// images. No corresponding dummy data is placed in ceph cluster 1 because the test
213+
// focuses on this one-directional isolation: cluster 1 must not interfere with cluster 2.
214+
114215
It("create the dummy RBD image & snap in the ceph cluster 2 with the same name as ceph cluster 1", func() {
115216
By("get source image name from the PV/PVC")
116217
pvName, err := getPVFromPVC(test.tenantNamespace1, test.pvcName)
@@ -136,7 +237,7 @@ func (test *multiRookCephTest) testMain() {
136237
Expect(err).NotTo(HaveOccurred())
137238
})
138239

139-
It("check the rbd images in the both clusters", func() {
240+
It("check the rbd images in both clusters after cluster 1 backup", func() {
140241
imageAndSnaps1, err := getImageAndSnapNames(cephCluster1Namespace, test.poolName)
141242
Expect(err).NotTo(HaveOccurred())
142243
Expect(imageAndSnaps1).To(ConsistOf(expectImageAndSnaps1))
@@ -146,8 +247,24 @@ func (test *multiRookCephTest) testMain() {
146247
Expect(imageAndSnaps2).To(ConsistOf(expectImageAndSnaps1))
147248
})
148249

149-
It("create PVC, backup, and restore in the tenant namespace 2", func() {
150-
test.createPVCBackupRestoreWithData(test.tenantNamespace2, test.storageClassName2, testData2)
250+
// --- Cluster 2 backup lifecycle ---
251+
252+
It("create PVC and write data in tenant namespace 2", func() {
253+
err := applyPVCTemplate(test.tenantNamespace2, test.pvcName, test.storageClassName2)
254+
Expect(err).NotTo(HaveOccurred())
255+
err = writeTestData(test.tenantNamespace2, test.pvcName, testData2)
256+
Expect(err).NotTo(HaveOccurred())
257+
})
258+
259+
It("create backup in tenant namespace 2 and wait for snapshot", func() {
260+
err := applyMantleBackupTemplate(test.tenantNamespace2, test.pvcName, test.mantleBackupName)
261+
Expect(err).NotTo(HaveOccurred())
262+
263+
Eventually(func(g Gomega) {
264+
ok, err := isMantleBackupSnapshotCaptured(test.tenantNamespace2, test.mantleBackupName)
265+
g.Expect(err).NotTo(HaveOccurred())
266+
g.Expect(ok).To(BeTrue())
267+
}).Should(Succeed())
151268

152269
pvName, err := getPVFromPVC(test.tenantNamespace2, test.pvcName)
153270
Expect(err).NotTo(HaveOccurred())
@@ -160,6 +277,73 @@ func (test *multiRookCephTest) testMain() {
160277
}
161278
})
162279

280+
It("check labels on MantleBackup after backup in tenant namespace 2", func() {
281+
pvc, err := getObject[corev1.PersistentVolumeClaim]("pvc", test.tenantNamespace2, test.pvcName)
282+
Expect(err).NotTo(HaveOccurred())
283+
284+
mb, err := getMB(test.tenantNamespace2, test.mantleBackupName)
285+
Expect(err).NotTo(HaveOccurred())
286+
287+
Expect(mb.GetLabels()).To(HaveKeyWithValue("mantle.cybozu.io/cluster-id", cephCluster2Namespace))
288+
Expect(mb.GetLabels()).To(HaveKeyWithValue("mantle.cybozu.io/local-backup-target-pvc-uid", string(pvc.GetUID())))
289+
})
290+
291+
It("verify for tenant namespace 2 runs only in ceph cluster 2, is cleaned up, and never appears in ceph cluster 1", func() {
292+
// The MantleBackup already exists, so the verify resource names (derived from its
293+
// UID) are known now, before verify runs. This lets us start monitoring ceph cluster 1
294+
// from the beginning of the verify lifecycle.
295+
mb, err := getMB(test.tenantNamespace2, test.mantleBackupName)
296+
Expect(err).NotTo(HaveOccurred())
297+
backup2 = mb
298+
299+
jobName := controller.MakeVerifyJobName(backup2)
300+
pvcName := controller.MakeVerifyPVCName(backup2)
301+
302+
// Continuously monitor until ceph cluster 2 finishes verify and cleans up its
303+
// resources. On every poll, hard-assert (via the outer Expect, not g.Expect) that
304+
// the verify resources never appear in ceph cluster 1, so even a transient
305+
// appearance fails the test immediately instead of being tolerated as a retryable
306+
// condition. A single point-in-time check would miss resources that are created
307+
// briefly in the wrong namespace and then cleaned up quickly.
308+
Eventually(func(g Gomega) {
309+
// The verify resources must NEVER appear in ceph cluster 1 at any point, so it doesn't use 'g'.
310+
existsJob1, err := checkJobExists(cephCluster1Namespace, jobName)
311+
Expect(err).NotTo(HaveOccurred())
312+
Expect(existsJob1).To(BeFalse(), "verify job must not run in ceph cluster 1")
313+
existsPVC1, err := checkPVCExists(cephCluster1Namespace, pvcName)
314+
Expect(err).NotTo(HaveOccurred())
315+
Expect(existsPVC1).To(BeFalse(), "verify PVC must not be created in ceph cluster 1")
316+
317+
mb, err := getMB(test.tenantNamespace2, test.mantleBackupName)
318+
g.Expect(err).NotTo(HaveOccurred())
319+
// If the ceph cluster 1 controller mistakenly runs verify for this backup,
320+
// the verify job fails because the RBD snapshot does not exist in ceph cluster 1,
321+
// which causes IsVerifiedFalse to be set instead of IsVerifiedTrue.
322+
Expect(mb.IsVerifiedFalse()).To(BeFalse(),
323+
"verify failed: ceph cluster 1 controller may have run verify for ceph cluster 2's backup")
324+
325+
// Terminating condition: verify succeeded in ceph cluster 2 and its resources
326+
// have been cleaned up.
327+
g.Expect(mb.IsVerifiedTrue()).To(BeTrue())
328+
existsJob2, err := checkJobExists(cephCluster2Namespace, jobName)
329+
g.Expect(err).NotTo(HaveOccurred())
330+
g.Expect(existsJob2).To(BeFalse(), "verify job should be cleaned up from ceph cluster 2")
331+
existsPVC2, err := checkPVCExists(cephCluster2Namespace, pvcName)
332+
g.Expect(err).NotTo(HaveOccurred())
333+
g.Expect(existsPVC2).To(BeFalse(), "verify PVC should be cleaned up from ceph cluster 2")
334+
}).Should(Succeed())
335+
})
336+
337+
It("create restore in tenant namespace 2 and wait for ready", func() {
338+
err := applyMantleRestoreTemplate(test.tenantNamespace2, test.mantleRestoreName, test.mantleBackupName)
339+
Expect(err).NotTo(HaveOccurred())
340+
Eventually(func() bool {
341+
return isMantleRestoreReady(test.tenantNamespace2, test.mantleRestoreName)
342+
}).Should(BeTrue())
343+
})
344+
345+
// --- Cross-cluster RBD state and data integrity checks ---
346+
163347
It("check the rbd images in the both clusters", func() {
164348
imageAndSnaps1, err := getImageAndSnapNames(cephCluster1Namespace, test.poolName)
165349
Expect(err).NotTo(HaveOccurred())
@@ -176,6 +360,8 @@ func (test *multiRookCephTest) testMain() {
176360
Expect(backupData2).To(Equal(testData2))
177361
})
178362

363+
// --- Deletion isolation checks ---
364+
179365
It("delete the resources in the tenant namespace 1", func() {
180366
test.deleteBackupRestore(test.tenantNamespace1)
181367
})
@@ -230,23 +416,6 @@ func (test *multiRookCephTest) testMain() {
230416
})
231417
}
232418

233-
func (test *multiRookCephTest) createPVCBackupRestoreWithData(ns, sc string, data []byte) {
234-
err := applyPVCTemplate(ns, test.pvcName, sc)
235-
Expect(err).NotTo(HaveOccurred())
236-
err = writeTestData(ns, test.pvcName, data)
237-
Expect(err).NotTo(HaveOccurred())
238-
239-
err = applyMantleBackupTemplate(ns, test.pvcName, test.mantleBackupName)
240-
Expect(err).NotTo(HaveOccurred())
241-
242-
err = applyMantleRestoreTemplate(ns, test.mantleRestoreName, test.mantleBackupName)
243-
Expect(err).NotTo(HaveOccurred())
244-
245-
Eventually(func() bool {
246-
return isMantleRestoreReady(ns, test.mantleRestoreName)
247-
}).Should(BeTrue())
248-
}
249-
250419
func (test *multiRookCephTest) deleteBackupRestore(ns string) {
251420
_, stderr, err := kubectl("delete", "mantlerestore", "-n", ns, test.mantleRestoreName)
252421
Expect(err).NotTo(HaveOccurred(), string(stderr))

test/e2e/singlek8s/util.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,3 +538,18 @@ func checkJobExists(namespace, jobName string) (bool, error) {
538538

539539
return found, nil
540540
}
541+
542+
func checkPVCExists(namespace, pvcName string) (bool, error) {
543+
stdout, _, err := kubectl("get", "pvc", "-n", namespace, "-o", "json")
544+
if err != nil {
545+
return false, err
546+
}
547+
var pvcList corev1.PersistentVolumeClaimList
548+
if err := json.Unmarshal(stdout, &pvcList); err != nil {
549+
return false, err
550+
}
551+
552+
return slices.ContainsFunc(pvcList.Items, func(pvc corev1.PersistentVolumeClaim) bool {
553+
return pvc.Name == pvcName
554+
}), nil
555+
}

0 commit comments

Comments
 (0)