Skip to content

Commit 58e52b9

Browse files
committed
implement e2e test for lock feature
Signed-off-by: Yuji Ito <[email protected]>
1 parent 23e36eb commit 58e52b9

File tree

7 files changed

+232
-19
lines changed

7 files changed

+232
-19
lines changed

internal/controller/finbackup_controller.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const (
4545
labelComponentDeletionJob = "deletion-job"
4646

4747
// Annotations
48-
annotationBackupTargetRBDImage = "fin.cybozu.io/backup-target-rbd-image"
48+
AnnotationBackupTargetRBDImage = "fin.cybozu.io/backup-target-rbd-image"
4949
annotationDiffFrom = "fin.cybozu.io/diff-from"
5050
annotationFinBackupName = "fin.cybozu.io/finbackup-name"
5151
annotationFinBackupNamespace = "fin.cybozu.io/finbackup-namespace"
@@ -390,7 +390,7 @@ func (r *FinBackupReconciler) createSnapshot(ctx context.Context, backup *finv1.
390390
if annotations == nil {
391391
annotations = map[string]string{}
392392
}
393-
annotations[annotationBackupTargetRBDImage] = rbdImage
393+
annotations[AnnotationBackupTargetRBDImage] = rbdImage
394394
annotations[annotationRBDPool] = rbdPool
395395
backup.SetAnnotations(annotations)
396396

@@ -723,7 +723,7 @@ func (r *FinBackupReconciler) getRBDPoolAndImageFromPVC(
723723

724724
func (r *FinBackupReconciler) getRBDPoolAndImage(ctx context.Context, backup *finv1.FinBackup) (string, string, error) {
725725
rbdPool := backup.GetAnnotations()[annotationRBDPool]
726-
rbdImage := backup.GetAnnotations()[annotationBackupTargetRBDImage]
726+
rbdImage := backup.GetAnnotations()[AnnotationBackupTargetRBDImage]
727727
if rbdPool != "" && rbdImage != "" {
728728
return rbdPool, rbdImage, nil
729729
}
@@ -884,7 +884,7 @@ func (r *FinBackupReconciler) createOrUpdateBackupJob(
884884
},
885885
{
886886
Name: "RBD_IMAGE_NAME",
887-
Value: backup.GetAnnotations()[annotationBackupTargetRBDImage],
887+
Value: backup.GetAnnotations()[AnnotationBackupTargetRBDImage],
888888
},
889889
{
890890
Name: "BACKUP_SNAPSHOT_ID",

internal/controller/finbackup_controller_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ var _ = Describe("FinBackup Controller integration test", Ordered, func() {
326326
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(finbackup2), finbackup2)).Should(Succeed())
327327
Expect(finbackup2.GetLabels()).To(HaveKeyWithValue(labelBackupTargetPVCUID, string(pvc1.GetUID())))
328328
annotations := finbackup2.GetAnnotations()
329-
Expect(annotations).To(HaveKeyWithValue(annotationBackupTargetRBDImage, rbdImageName))
329+
Expect(annotations).To(HaveKeyWithValue(AnnotationBackupTargetRBDImage, rbdImageName))
330330
Expect(annotations).To(HaveKeyWithValue(annotationRBDPool, rbdPoolName))
331331

332332
// Incremental backup specific: the diff-from annotation should exist and point to the SnapID of the full backup.
@@ -813,7 +813,7 @@ var _ = Describe("FinBackup Controller Reconcile Test", Ordered, func() {
813813
var updated finv1.FinBackup
814814
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(finbackup), &updated)).Should(Succeed())
815815
Expect(updated.GetLabels()).To(HaveKeyWithValue(labelBackupTargetPVCUID, string(pvc.GetUID())))
816-
Expect(updated.GetAnnotations()).To(HaveKeyWithValue(annotationBackupTargetRBDImage, rbdImageName))
816+
Expect(updated.GetAnnotations()).To(HaveKeyWithValue(AnnotationBackupTargetRBDImage, rbdImageName))
817817
Expect(updated.GetAnnotations()).To(HaveKeyWithValue(annotationRBDPool, rbdPoolName))
818818
})
819819
})

test/e2e/e2e_suite_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ var _ = Describe("Fin", func() {
7474
Context("wait environment", waitEnvironment)
7575
Context("full backup", Label("full-backup"), Ordered, fullBackupTestSuite)
7676
Context("incremental backup", Label("incremental-backup"), Ordered, incrementalBackupTestSuite)
77+
Context("lock", Label("lock"), Label("misc"), Ordered, lockTestSuite)
7778
Context("verification", Label("verification"), Label("misc"), Ordered, verificationTestSuite)
7879
Context("delete incremental backup", Label("delete-incremental-backup"), Label("misc"), Ordered,
7980
deleteIncrementalBackupTestSuite)

test/e2e/full_backup_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ func fullBackupTestSuite() {
5151
It("should create full backup", func(ctx SpecContext) {
5252
finbackup = CreateBackup(ctx, ctrlClient, rookNamespace, pvc, nodes[0])
5353

54+
var err error
55+
finbackup, err = WaitForFinBackupStoredToNodeAndVerified(ctx, ctrlClient, finbackup, 5*time.Minute)
56+
Expect(err).NotTo(HaveOccurred())
57+
5458
VerifyRawImage(pvc, nodes[0], writtenData)
5559
})
5660

@@ -195,7 +199,8 @@ func fullBackupTestSuite() {
195199

196200
VerifyNonExistenceOfRawImage(pvc, nodes[0])
197201
VerifyDeletionOfJobsForBackup(ctx, k8sClient, finbackup)
198-
VerifyDeletionOfSnapshotInFinBackup(ctx, ctrlClient, finbackup)
202+
err = VerifyDeletionOfSnapshotInFinBackup(ctx, finbackup)
203+
Expect(err).NotTo(HaveOccurred())
199204
})
200205

201206
AfterAll(func(ctx SpecContext) {

test/e2e/incremental_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ func incrementalBackupTestSuite() {
3838

3939
finbackup1 = CreateBackup(ctx, ctrlClient, rookNamespace, pvc, nodes[0])
4040

41+
var err error
42+
finbackup1, err = WaitForFinBackupStoredToNodeAndVerified(ctx, ctrlClient, finbackup1, 5*time.Minute)
43+
Expect(err).NotTo(HaveOccurred())
44+
4145
By("verifying the data in raw.img from the full backup")
4246
volumePath = filepath.Join("/fin", ns.Name, pvc.Name)
4347
// `--native-ssh=false` is used to avoid issues of conversion from LF to CRLF.
@@ -240,7 +244,8 @@ func incrementalBackupTestSuite() {
240244
Expect(rawImageData).To(Equal(dataOnIncrementalBackup), "Data in raw.img does not match the expected data")
241245

242246
VerifyDeletionOfJobsForBackup(ctx, k8sClient, finbackup1)
243-
VerifyDeletionOfSnapshotInFinBackup(ctx, ctrlClient, finbackup1)
247+
err = VerifyDeletionOfSnapshotInFinBackup(ctx, finbackup1)
248+
Expect(err).NotTo(HaveOccurred())
244249
})
245250

246251
// Description:

test/e2e/lock_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package e2e
2+
3+
import (
4+
"encoding/json"
5+
"time"
6+
7+
finv1 "github.com/cybozu-go/fin/api/v1"
8+
"github.com/cybozu-go/fin/internal/model"
9+
"github.com/cybozu-go/fin/test/utils"
10+
. "github.com/onsi/ginkgo/v2"
11+
. "github.com/onsi/gomega"
12+
corev1 "k8s.io/api/core/v1"
13+
)
14+
15+
func lockTestSuite() {
16+
var ns *corev1.Namespace
17+
var pvc *corev1.PersistentVolumeClaim
18+
var fb *finv1.FinBackup
19+
dummyLockID := "dummy-lock-id"
20+
var poolName, imageName string
21+
22+
It("should setup environment", func(ctx SpecContext) {
23+
ns = NewNamespace(utils.GetUniqueName("ns-"))
24+
err := CreateNamespace(ctx, k8sClient, ns)
25+
Expect(err).NotTo(HaveOccurred())
26+
27+
pvc = CreateBackupTargetPVC(ctx, k8sClient, ns, "Filesystem", rookStorageClass, "ReadWriteOnce", "100Mi")
28+
pvc, err = WaitForPVCBound(ctx, ctrlClient, pvc, 1*time.Minute)
29+
Expect(err).NotTo(HaveOccurred())
30+
})
31+
32+
It("should lock the volume", func(ctx SpecContext) {
33+
pv, err := GetPvByPvc(ctx, k8sClient, pvc)
34+
Expect(err).NotTo(HaveOccurred())
35+
poolName = pv.Spec.CSI.VolumeAttributes["pool"]
36+
imageName = pv.Spec.CSI.VolumeAttributes["imageName"]
37+
38+
// locked
39+
_, _, err = kubectl("exec", "-n", rookNamespace, "deployment/"+finDeploymentName, "--",
40+
"rbd", "-p", poolName, "lock", "add", imageName, dummyLockID)
41+
Expect(err).NotTo(HaveOccurred())
42+
})
43+
44+
It("should create backup and wait for the log", func(ctx SpecContext) {
45+
var err error
46+
fb, err = NewFinBackup(rookNamespace, utils.GetUniqueName("fb-"), pvc, nodes[0])
47+
Expect(err).NotTo(HaveOccurred())
48+
err = CreateFinBackup(ctx, ctrlClient, fb)
49+
Expect(err).NotTo(HaveOccurred())
50+
51+
err = WaitControllerLog(ctx,
52+
"the volume is locked by another process.*"+string(fb.GetUID()),
53+
3*time.Minute)
54+
Expect(err).NotTo(HaveOccurred())
55+
})
56+
57+
It("checks that the snapshot is not created", func(ctx SpecContext) {
58+
snapshots, err := ListRBDSnapshots(ctx, poolName, imageName)
59+
Expect(err).NotTo(HaveOccurred())
60+
Expect(snapshots).To(BeEmpty())
61+
})
62+
63+
It("should unlock the volume", func() {
64+
stdout, _, err := kubectl("exec", "-n", rookNamespace, "deployment/"+finDeploymentName, "--",
65+
"rbd", "-p", poolName, "--format", "json", "lock", "ls", imageName)
66+
Expect(err).NotTo(HaveOccurred())
67+
var locks []*model.RBDLock
68+
err = json.Unmarshal(stdout, &locks)
69+
Expect(err).NotTo(HaveOccurred())
70+
Expect(locks).To(HaveLen(1))
71+
72+
// unlock
73+
_, _, err = kubectl("exec", "-n", rookNamespace, "deployment/"+finDeploymentName, "--",
74+
"rbd", "-p", poolName, "lock", "rm", imageName, dummyLockID, locks[0].Locker)
75+
Expect(err).NotTo(HaveOccurred())
76+
})
77+
78+
It("should resume backup creation and complete it", func(ctx SpecContext) {
79+
_, err := WaitForFinBackupStoredToNodeAndVerified(ctx, ctrlClient, fb, 1*time.Minute)
80+
Expect(err).NotTo(HaveOccurred())
81+
})
82+
83+
It("should not exist locks after backup completion", func() {
84+
stdout, _, err := kubectl("exec", "-n", rookNamespace, "deployment/"+finDeploymentName, "--",
85+
"rbd", "-p", poolName, "--format", "json", "lock", "ls", imageName)
86+
Expect(err).NotTo(HaveOccurred())
87+
var locks []*model.RBDLock
88+
err = json.Unmarshal(stdout, &locks)
89+
Expect(err).NotTo(HaveOccurred())
90+
Expect(locks).To(HaveLen(0))
91+
})
92+
}

test/e2e/util_test.go

Lines changed: 121 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
package e2e
22

33
import (
4+
"bufio"
45
"bytes"
56
"context"
7+
"encoding/json"
68
"fmt"
79
"html/template"
810
"os"
911
"os/exec"
1012
"path/filepath"
13+
"regexp"
1114
"time"
1215

1316
finv1 "github.com/cybozu-go/fin/api/v1"
17+
"github.com/cybozu-go/fin/internal/controller"
18+
"github.com/cybozu-go/fin/internal/model"
1419
"github.com/cybozu-go/fin/test/utils"
1520
. "github.com/onsi/ginkgo/v2"
1621
. "github.com/onsi/gomega"
@@ -26,6 +31,7 @@ import (
2631
)
2732

2833
const (
34+
finDeploymentName = "fin-controller-manager"
2935
rookNamespace = "rook-ceph"
3036
rookStorageClass = "rook-ceph-block"
3137
poolName = "rook-ceph-block-pool"
@@ -80,7 +86,7 @@ func checkDeploymentReady(namespace, name string) error {
8086
func waitEnvironment() {
8187
It("wait for fin-controller to be ready", func() {
8288
Eventually(func() error {
83-
return checkDeploymentReady(rookNamespace, "fin-controller-manager")
89+
return checkDeploymentReady(rookNamespace, finDeploymentName)
8490
}).Should(Succeed())
8591
})
8692
}
@@ -372,16 +378,31 @@ func DeleteFinRestore(ctx context.Context, client client.Client, finrestore *fin
372378
return client.Delete(ctx, target)
373379
}
374380

375-
func WaitForFinBackupStoredToNodeAndVerified(ctx context.Context, c client.Client, finbackup *finv1.FinBackup, timeout time.Duration) error {
376-
return wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
377-
fb := &finv1.FinBackup{}
378-
err := c.Get(ctx, client.ObjectKeyFromObject(finbackup), fb)
381+
func WaitForPVCBound(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim, timeout time.Duration) (*corev1.PersistentVolumeClaim, error) {
382+
GinkgoHelper()
383+
currentPVC := &corev1.PersistentVolumeClaim{}
384+
err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
385+
err := c.Get(ctx, client.ObjectKeyFromObject(pvc), currentPVC)
386+
if err != nil {
387+
return false, err
388+
}
389+
return currentPVC.Status.Phase == corev1.ClaimBound, nil
390+
})
391+
return currentPVC, err
392+
}
393+
394+
func WaitForFinBackupStoredToNodeAndVerified(ctx context.Context, c client.Client, finbackup *finv1.FinBackup, timeout time.Duration) (*finv1.FinBackup, error) {
395+
GinkgoHelper()
396+
currentFB := &finv1.FinBackup{}
397+
err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
398+
err := c.Get(ctx, client.ObjectKeyFromObject(finbackup), currentFB)
379399
if err != nil {
380400
return false, err
381401
}
382402

383-
return fb.IsStoredToNode() && fb.IsVerifiedTrue(), nil
403+
return currentFB.IsStoredToNode() && currentFB.IsVerifiedTrue(), nil
384404
})
405+
return currentFB, err
385406
}
386407

387408
func WaitForFinRestoreReady(ctx context.Context, c client.Client, finrestore *finv1.FinRestore, timeout time.Duration) error {
@@ -455,6 +476,58 @@ func WaitForPVCDeletion(ctx context.Context, k8sClient kubernetes.Interface, pvc
455476
})
456477
}
457478

479+
// WaitControllerLog waits until the controller log matches the given pattern or the duration is exceeded.
480+
func WaitControllerLog(ctx SpecContext, pattern string, duration time.Duration) error {
481+
GinkgoHelper()
482+
483+
timeoutCtx, cancel := context.WithTimeout(ctx, duration)
484+
defer cancel()
485+
486+
matcher := regexp.MustCompile(pattern)
487+
488+
command := exec.CommandContext(timeoutCtx, "kubectl", "logs", "-n", rookNamespace, "deployment/"+finDeploymentName, "-f")
489+
stdoutPipe, err := command.StdoutPipe()
490+
if err != nil {
491+
panic(err)
492+
}
493+
err = command.Start()
494+
if err != nil {
495+
panic(err)
496+
}
497+
defer func() {
498+
_ = command.Process.Kill()
499+
_ = command.Wait()
500+
}()
501+
502+
// read stdout line by line until the pattern is found
503+
scanner := bufio.NewScanner(stdoutPipe)
504+
found := make(chan struct{})
505+
go func() {
506+
for scanner.Scan() {
507+
select {
508+
case <-timeoutCtx.Done():
509+
return
510+
default:
511+
}
512+
line := scanner.Text()
513+
if matcher.MatchString(line) {
514+
close(found)
515+
return
516+
}
517+
}
518+
if scanner.Err() != nil {
519+
panic(scanner.Err())
520+
}
521+
}()
522+
523+
select {
524+
case <-timeoutCtx.Done():
525+
return timeoutCtx.Err()
526+
case <-found:
527+
return nil
528+
}
529+
}
530+
458531
func VerifySizeOfRestorePVC(ctx context.Context, c client.Client, restore *finv1.FinRestore) {
459532
GinkgoHelper()
460533

@@ -679,6 +752,30 @@ func GetNodeNames(ctx context.Context, k8sClient kubernetes.Interface) ([]string
679752
return nodeNames, nil
680753
}
681754

755+
func GetPvByPvc(ctx context.Context, k8sClient kubernetes.Interface, pvc *corev1.PersistentVolumeClaim) (*corev1.PersistentVolume, error) {
756+
GinkgoHelper()
757+
758+
return k8sClient.CoreV1().PersistentVolumes().Get(ctx, pvc.Spec.VolumeName, metav1.GetOptions{})
759+
}
760+
761+
func ListRBDSnapshots(ctx context.Context, poolName, imageName string) ([]*model.RBDSnapshot, error) {
762+
GinkgoHelper()
763+
764+
stdout, stderr, err := kubectl("exec", "-n", rookNamespace, "deploy/rook-ceph-tools", "--",
765+
"rbd", "-p", poolName, "snap", "ls", imageName, "--format", "json")
766+
if err != nil {
767+
return nil, fmt.Errorf("failed to list RBD snapshots. stdout: %s, stderr: %s, err: %w",
768+
string(stdout), string(stderr), err)
769+
}
770+
771+
var snapshots []*model.RBDSnapshot
772+
if err := json.Unmarshal(stdout, &snapshots); err != nil {
773+
return nil, fmt.Errorf("failed to unmarshal RBD snapshot list. err: %w", err)
774+
}
775+
776+
return snapshots, nil
777+
}
778+
682779
func VerifyRawImage(pvc *corev1.PersistentVolumeClaim, node string, expected []byte) {
683780
GinkgoHelper()
684781

@@ -706,13 +803,26 @@ func VerifyDeletionOfJobsForBackup(ctx context.Context, client kubernetes.Interf
706803
Expect(err).NotTo(HaveOccurred(), "Deletion job should be deleted.")
707804
}
708805

709-
func VerifyDeletionOfSnapshotInFinBackup(ctx context.Context, ctrlClient client.Client, finbackup *finv1.FinBackup) {
806+
func VerifyDeletionOfSnapshotInFinBackup(ctx context.Context, finbackup *finv1.FinBackup) error {
710807
GinkgoHelper()
711808

712-
rbdImage := finbackup.Annotations["fin.cybozu.io/backup-target-rbd-image"]
713-
stdout, stderr, err := kubectl("exec", "-n", rookNamespace, "deploy/rook-ceph-tools", "--",
714-
"rbd", "info", fmt.Sprintf("%s/%s@fin-backup-%s", poolName, rbdImage, finbackup.UID))
715-
Expect(err).To(HaveOccurred(), "Snapshot should be deleted. stdout: %s, stderr: %s", stdout, stderr)
809+
imageName := finbackup.Annotations[controller.AnnotationBackupTargetRBDImage]
810+
if len(imageName) == 0 {
811+
return fmt.Errorf("finbackup %s/%s does not have %s annotation",
812+
finbackup.Namespace, finbackup.Name, controller.AnnotationBackupTargetRBDImage)
813+
}
814+
snapshots, err := ListRBDSnapshots(ctx, poolName, imageName)
815+
if err != nil {
816+
return err
817+
}
818+
819+
expectedSnapName := fmt.Sprintf("fin-backup-%s", finbackup.UID)
820+
for _, snapshot := range snapshots {
821+
if snapshot.Name == expectedSnapName {
822+
return fmt.Errorf("snapshot %s still exists", expectedSnapName)
823+
}
824+
}
825+
return nil
716826
}
717827

718828
func VerifyDeletionOfResourcesForRestore(

0 commit comments

Comments
 (0)