From 9a4ebfc7e05b8b7015dc3fc37756def1a0c03711 Mon Sep 17 00:00:00 2001 From: Shinya Hayashi Date: Fri, 18 Oct 2024 08:05:49 +0000 Subject: [PATCH 1/4] add tests for prepareForDataSynchronization func Signed-off-by: Shinya Hayashi --- Makefile | 1 + .../mantlebackup_controller_test.go | 274 ++++++++++++++++++ .../proto/controller_grpc.pb_mock.go | 216 ++++++++++++++ 3 files changed, 491 insertions(+) create mode 100644 pkg/controller/proto/controller_grpc.pb_mock.go diff --git a/Makefile b/Makefile index 34cc8891..3f1f0e70 100644 --- a/Makefile +++ b/Makefile @@ -83,6 +83,7 @@ vet: ## Run go vet against code. .PHONY: mock mock: mockgen $(MOCKGEN) -source=internal/ceph/command.go -destination=internal/ceph/command_mock.go -package=ceph + $(MOCKGEN) -source=pkg/controller/proto/controller_grpc.pb.go -destination=pkg/controller/proto/controller_grpc.pb_mock.go -package=proto .PHONY: test test: manifests generate fmt vet envtest mock ## Run tests. diff --git a/internal/controller/mantlebackup_controller_test.go b/internal/controller/mantlebackup_controller_test.go index fd31a793..489ce352 100644 --- a/internal/controller/mantlebackup_controller_test.go +++ b/internal/controller/mantlebackup_controller_test.go @@ -3,6 +3,7 @@ package controller import ( "context" "encoding/json" + "fmt" "slices" "sync" "time" @@ -12,6 +13,7 @@ import ( "github.com/cybozu-go/mantle/pkg/controller/proto" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "go.uber.org/mock/gomock" "google.golang.org/grpc" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" @@ -19,6 +21,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" "k8s.io/kube-openapi/pkg/validation/strfmt" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/event" ) @@ -547,3 +550,274 @@ var _ = Describe("searchDiffOriginMantleBackup", func() { true, "test1"), ) }) + +type reporter struct{} + +func (g reporter) Errorf(format string, args ...any) { + Fail(fmt.Sprintf(format, args...)) +} + +func (g reporter) Fatalf(format string, args ...any) { + Fail(fmt.Sprintf(format, args...)) +} + +func newMantleBackup( + name string, + namespace string, + annotations map[string]string, + labels map[string]string, + withDelTimestamp bool, + snapID int, + readyToUse metav1.ConditionStatus, + syncedToRemote metav1.ConditionStatus, +) *mantlev1.MantleBackup { + newMB := &mantlev1.MantleBackup{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: annotations, + Labels: labels, + }, + Status: mantlev1.MantleBackupStatus{ + SnapID: &snapID, + }, + } + if withDelTimestamp { + now := metav1.Now() + newMB.SetDeletionTimestamp(&now) + } + meta.SetStatusCondition(&newMB.Status.Conditions, metav1.Condition{ + Type: mantlev1.BackupConditionReadyToUse, + Status: readyToUse, + }) + meta.SetStatusCondition(&newMB.Status.Conditions, metav1.Condition{ + Type: mantlev1.BackupConditionSyncedToRemote, + Status: syncedToRemote, + }) + return newMB +} + +var _ = Describe("prepareForDataSynchronization", func() { + testPVCUID := "d3b07384-d9a7-4e6b-8a3b-1f4b7b7b7b7b" + testName := "testSnap5" + testNamespace := "test-ns" + primaryLabels := map[string]string{ + labelLocalBackupTargetPVCUID: testPVCUID, + } + secondaryLabels := map[string]string{ + labelRemoteBackupTargetPVCUID: testPVCUID, + } + testMantleBackup := newMantleBackup(testName, testNamespace, nil, primaryLabels, + false, 5, metav1.ConditionTrue, metav1.ConditionFalse) + + doTest := func( + backup *mantlev1.MantleBackup, + primaryBackupsWithoutTarget []*mantlev1.MantleBackup, + secondaryMantleBackups []*mantlev1.MantleBackup, + isIncremental bool, + isSecondaryMantleBackupReadyToUse bool, + diffFrom *mantlev1.MantleBackup, + ) { + var t reporter + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + grpcClient := proto.NewMockMantleServiceClient(mockCtrl) + + data, err := json.Marshal(secondaryMantleBackups) + Expect(err).NotTo(HaveOccurred()) + grpcClient.EXPECT().ListMantleBackup(gomock.Any(), + &proto.ListMantleBackupRequest{ + PvcUID: testPVCUID, + Namespace: testNamespace, + }).Times(1).Return( + &proto.ListMantleBackupResponse{ + MantleBackupList: data, + }, nil) + + ctrlClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() + Expect(ctrlClient).NotTo(BeNil()) + primaryBackups := append(primaryBackupsWithoutTarget, backup.DeepCopy()) + for _, backup := range primaryBackups { + shouldBeDeleted := false + if !backup.DeletionTimestamp.IsZero() { + shouldBeDeleted = true + } + + err := ctrlClient.Create(context.Background(), backup) + Expect(err).NotTo(HaveOccurred()) + if shouldBeDeleted { + // Add a finalizer to prevent the immediate deletion. + _ = controllerutil.AddFinalizer(backup, MantleBackupFinalizerName) + err = ctrlClient.Update(context.Background(), backup) + Expect(err).NotTo(HaveOccurred()) + err := ctrlClient.Delete(context.Background(), backup) + Expect(err).NotTo(HaveOccurred()) + // Check if the deletion is blocked by the finalizer. + err = ctrlClient.Get(context.Background(), types.NamespacedName{ + Namespace: backup.Namespace, + Name: backup.Name, + }, backup) + Expect(err).NotTo(HaveOccurred()) + Expect(controllerutil.ContainsFinalizer(backup, MantleBackupFinalizerName)).To(BeTrue()) + } + } + + mbr := NewMantleBackupReconciler(ctrlClient, + ctrlClient.Scheme(), "test", RolePrimary, nil) + + ret, err := mbr.prepareForDataSynchronization(context.Background(), + backup, grpcClient) + Expect(err).NotTo(HaveOccurred()) + Expect(ret.isIncremental).To(Equal(isIncremental)) + Expect(ret.isSecondaryMantleBackupReadyToUse).To(Equal(isSecondaryMantleBackupReadyToUse)) + if isIncremental { + Expect(ret.diffFrom).NotTo(BeNil()) + Expect(ret.diffFrom.GetName()).To(Equal(diffFrom.GetName())) + } else { + Expect(ret.diffFrom).To(BeNil()) + } + } + + DescribeTable("MantleBackup without annotations", doTest, + Entry("No synced MantleBackup exists", + testMantleBackup, + []*mantlev1.MantleBackup{}, + []*mantlev1.MantleBackup{ + newMantleBackup(testName, testNamespace, nil, secondaryLabels, false, + 5, metav1.ConditionFalse, metav1.ConditionUnknown), + }, false, false, nil), + Entry("Synced but not reflected to the condition", + testMantleBackup, + []*mantlev1.MantleBackup{}, + []*mantlev1.MantleBackup{ + newMantleBackup(testName, testNamespace, nil, secondaryLabels, false, + 5, metav1.ConditionTrue, metav1.ConditionUnknown), + }, false, true, nil), + Entry("Synced MantleBackup exists but deletionTimestamp is set on both clusters", + testMantleBackup, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap3", testNamespace, nil, primaryLabels, true, + 3, metav1.ConditionTrue, metav1.ConditionTrue), + }, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap3", testNamespace, nil, secondaryLabels, true, + 3, metav1.ConditionTrue, metav1.ConditionUnknown), + newMantleBackup(testName, testNamespace, nil, secondaryLabels, false, + 5, metav1.ConditionFalse, metav1.ConditionUnknown), + }, false, false, nil), + Entry("Synced MantleBackup exists but deletionTimestamp is set on the primary cluster", + testMantleBackup, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap3", testNamespace, nil, primaryLabels, true, + 3, metav1.ConditionTrue, metav1.ConditionTrue), + }, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap3", testNamespace, nil, secondaryLabels, false, + 3, metav1.ConditionTrue, metav1.ConditionUnknown), + newMantleBackup(testName, testNamespace, nil, secondaryLabels, false, + 5, metav1.ConditionFalse, metav1.ConditionUnknown), + }, false, false, nil), + Entry("Synced MantleBackup exists but deletionTimestamp is set on the secondary cluster", + testMantleBackup, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap3", testNamespace, nil, primaryLabels, false, + 3, metav1.ConditionTrue, metav1.ConditionTrue), + }, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap3", testNamespace, nil, secondaryLabels, true, + 3, metav1.ConditionTrue, metav1.ConditionUnknown), + newMantleBackup(testName, testNamespace, nil, secondaryLabels, false, + 5, metav1.ConditionFalse, metav1.ConditionUnknown), + }, false, false, nil), + Entry("The candidate for the diff origin MantleBackup does not exist on the primary cluster", + testMantleBackup, + []*mantlev1.MantleBackup{}, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap3", testNamespace, nil, secondaryLabels, false, + 3, metav1.ConditionTrue, metav1.ConditionUnknown), + newMantleBackup(testName, testNamespace, nil, secondaryLabels, false, + 5, metav1.ConditionFalse, metav1.ConditionUnknown), + }, false, false, nil), + Entry("The candidate for the diff origin MantleBackup does not exist on the secondary cluster", + testMantleBackup, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap3", testNamespace, nil, primaryLabels, false, + 3, metav1.ConditionTrue, metav1.ConditionTrue), + }, + []*mantlev1.MantleBackup{ + newMantleBackup(testName, testNamespace, nil, secondaryLabels, false, + 5, metav1.ConditionFalse, metav1.ConditionUnknown), + }, false, false, nil), + Entry("Incremental backup", + testMantleBackup, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap1", testNamespace, nil, primaryLabels, false, + 1, metav1.ConditionTrue, metav1.ConditionTrue), + newMantleBackup("testSnap3", testNamespace, nil, primaryLabels, false, + 3, metav1.ConditionTrue, metav1.ConditionTrue), + }, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap1", testNamespace, nil, secondaryLabels, false, + 1, metav1.ConditionTrue, metav1.ConditionUnknown), + newMantleBackup("testSnap3", testNamespace, nil, secondaryLabels, false, + 3, metav1.ConditionTrue, metav1.ConditionUnknown), + newMantleBackup(testName, testNamespace, nil, secondaryLabels, false, + 5, metav1.ConditionFalse, metav1.ConditionUnknown), + }, true, false, newMantleBackup("testSnap3", testNamespace, nil, primaryLabels, + false, 3, metav1.ConditionTrue, metav1.ConditionTrue), + ), + Entry("Incremental backup but skips the MantleBackup which only exists on the primary cluster", + testMantleBackup, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap1", testNamespace, nil, primaryLabels, false, + 1, metav1.ConditionTrue, metav1.ConditionTrue), + newMantleBackup("testSnap3", testNamespace, nil, primaryLabels, false, + 3, metav1.ConditionTrue, metav1.ConditionTrue), + }, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap1", testNamespace, nil, secondaryLabels, false, + 1, metav1.ConditionTrue, metav1.ConditionUnknown), + newMantleBackup(testName, testNamespace, nil, secondaryLabels, false, + 5, metav1.ConditionFalse, metav1.ConditionUnknown), + }, true, false, newMantleBackup("testSnap1", testNamespace, nil, primaryLabels, + false, 1, metav1.ConditionTrue, metav1.ConditionTrue), + ), + ) + + syncModeFullAnnotation := map[string]string{ + annotSyncMode: syncModeFull, + } + syncModeIncAnnotation := map[string]string{ + annotSyncMode: syncModeIncremental, + annotDiffFrom: "testSnap3", + } + DescribeTable("MantleBackup with annotations", doTest, + Entry(`Skip search because the MantleBackup already has the "sync-mode: full" annotation.`, + newMantleBackup(testName, testNamespace, syncModeFullAnnotation, primaryLabels, + false, 5, metav1.ConditionTrue, metav1.ConditionFalse), + []*mantlev1.MantleBackup{}, + []*mantlev1.MantleBackup{ + newMantleBackup(testName, testNamespace, syncModeFullAnnotation, secondaryLabels, + false, 5, metav1.ConditionFalse, metav1.ConditionUnknown), + }, false, false, nil), + Entry(`Skip search because the MantleBackup already has the "sync-mode: incremental" annotation.`, + newMantleBackup(testName, testNamespace, syncModeIncAnnotation, primaryLabels, + false, 5, metav1.ConditionTrue, metav1.ConditionFalse), + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap1", testNamespace, nil, primaryLabels, false, + 1, metav1.ConditionTrue, metav1.ConditionTrue), + newMantleBackup("testSnap3", testNamespace, nil, primaryLabels, false, + 3, metav1.ConditionTrue, metav1.ConditionTrue), + }, + []*mantlev1.MantleBackup{ + newMantleBackup("testSnap1", testNamespace, nil, secondaryLabels, false, + 1, metav1.ConditionTrue, metav1.ConditionUnknown), + newMantleBackup("testSnap3", testNamespace, nil, secondaryLabels, false, + 3, metav1.ConditionTrue, metav1.ConditionUnknown), + newMantleBackup(testName, testNamespace, syncModeIncAnnotation, secondaryLabels, + false, 5, metav1.ConditionFalse, metav1.ConditionUnknown), + }, true, false, newMantleBackup("testSnap3", testNamespace, nil, primaryLabels, + false, 3, metav1.ConditionTrue, metav1.ConditionTrue), + ), + ) +}) diff --git a/pkg/controller/proto/controller_grpc.pb_mock.go b/pkg/controller/proto/controller_grpc.pb_mock.go new file mode 100644 index 00000000..43019c90 --- /dev/null +++ b/pkg/controller/proto/controller_grpc.pb_mock.go @@ -0,0 +1,216 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: pkg/controller/proto/controller_grpc.pb.go +// +// Generated by this command: +// +// mockgen -source=pkg/controller/proto/controller_grpc.pb.go -destination=pkg/controller/proto/controller_grpc.pb_mock.go -package=proto +// + +// Package proto is a generated GoMock package. +package proto + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + grpc "google.golang.org/grpc" +) + +// MockMantleServiceClient is a mock of MantleServiceClient interface. +type MockMantleServiceClient struct { + ctrl *gomock.Controller + recorder *MockMantleServiceClientMockRecorder +} + +// MockMantleServiceClientMockRecorder is the mock recorder for MockMantleServiceClient. +type MockMantleServiceClientMockRecorder struct { + mock *MockMantleServiceClient +} + +// NewMockMantleServiceClient creates a new mock instance. +func NewMockMantleServiceClient(ctrl *gomock.Controller) *MockMantleServiceClient { + mock := &MockMantleServiceClient{ctrl: ctrl} + mock.recorder = &MockMantleServiceClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMantleServiceClient) EXPECT() *MockMantleServiceClientMockRecorder { + return m.recorder +} + +// CreateOrUpdateMantleBackup mocks base method. +func (m *MockMantleServiceClient) CreateOrUpdateMantleBackup(ctx context.Context, in *CreateOrUpdateMantleBackupRequest, opts ...grpc.CallOption) (*CreateOrUpdateMantleBackupResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateOrUpdateMantleBackup", varargs...) + ret0, _ := ret[0].(*CreateOrUpdateMantleBackupResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateOrUpdateMantleBackup indicates an expected call of CreateOrUpdateMantleBackup. +func (mr *MockMantleServiceClientMockRecorder) CreateOrUpdateMantleBackup(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdateMantleBackup", reflect.TypeOf((*MockMantleServiceClient)(nil).CreateOrUpdateMantleBackup), varargs...) +} + +// CreateOrUpdatePVC mocks base method. +func (m *MockMantleServiceClient) CreateOrUpdatePVC(ctx context.Context, in *CreateOrUpdatePVCRequest, opts ...grpc.CallOption) (*CreateOrUpdatePVCResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateOrUpdatePVC", varargs...) + ret0, _ := ret[0].(*CreateOrUpdatePVCResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateOrUpdatePVC indicates an expected call of CreateOrUpdatePVC. +func (mr *MockMantleServiceClientMockRecorder) CreateOrUpdatePVC(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdatePVC", reflect.TypeOf((*MockMantleServiceClient)(nil).CreateOrUpdatePVC), varargs...) +} + +// ListMantleBackup mocks base method. +func (m *MockMantleServiceClient) ListMantleBackup(ctx context.Context, in *ListMantleBackupRequest, opts ...grpc.CallOption) (*ListMantleBackupResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ListMantleBackup", varargs...) + ret0, _ := ret[0].(*ListMantleBackupResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListMantleBackup indicates an expected call of ListMantleBackup. +func (mr *MockMantleServiceClientMockRecorder) ListMantleBackup(ctx, in any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMantleBackup", reflect.TypeOf((*MockMantleServiceClient)(nil).ListMantleBackup), varargs...) +} + +// MockMantleServiceServer is a mock of MantleServiceServer interface. +type MockMantleServiceServer struct { + ctrl *gomock.Controller + recorder *MockMantleServiceServerMockRecorder +} + +// MockMantleServiceServerMockRecorder is the mock recorder for MockMantleServiceServer. +type MockMantleServiceServerMockRecorder struct { + mock *MockMantleServiceServer +} + +// NewMockMantleServiceServer creates a new mock instance. +func NewMockMantleServiceServer(ctrl *gomock.Controller) *MockMantleServiceServer { + mock := &MockMantleServiceServer{ctrl: ctrl} + mock.recorder = &MockMantleServiceServerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMantleServiceServer) EXPECT() *MockMantleServiceServerMockRecorder { + return m.recorder +} + +// CreateOrUpdateMantleBackup mocks base method. +func (m *MockMantleServiceServer) CreateOrUpdateMantleBackup(arg0 context.Context, arg1 *CreateOrUpdateMantleBackupRequest) (*CreateOrUpdateMantleBackupResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateOrUpdateMantleBackup", arg0, arg1) + ret0, _ := ret[0].(*CreateOrUpdateMantleBackupResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateOrUpdateMantleBackup indicates an expected call of CreateOrUpdateMantleBackup. +func (mr *MockMantleServiceServerMockRecorder) CreateOrUpdateMantleBackup(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdateMantleBackup", reflect.TypeOf((*MockMantleServiceServer)(nil).CreateOrUpdateMantleBackup), arg0, arg1) +} + +// CreateOrUpdatePVC mocks base method. +func (m *MockMantleServiceServer) CreateOrUpdatePVC(arg0 context.Context, arg1 *CreateOrUpdatePVCRequest) (*CreateOrUpdatePVCResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateOrUpdatePVC", arg0, arg1) + ret0, _ := ret[0].(*CreateOrUpdatePVCResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateOrUpdatePVC indicates an expected call of CreateOrUpdatePVC. +func (mr *MockMantleServiceServerMockRecorder) CreateOrUpdatePVC(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdatePVC", reflect.TypeOf((*MockMantleServiceServer)(nil).CreateOrUpdatePVC), arg0, arg1) +} + +// ListMantleBackup mocks base method. +func (m *MockMantleServiceServer) ListMantleBackup(arg0 context.Context, arg1 *ListMantleBackupRequest) (*ListMantleBackupResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListMantleBackup", arg0, arg1) + ret0, _ := ret[0].(*ListMantleBackupResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListMantleBackup indicates an expected call of ListMantleBackup. +func (mr *MockMantleServiceServerMockRecorder) ListMantleBackup(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMantleBackup", reflect.TypeOf((*MockMantleServiceServer)(nil).ListMantleBackup), arg0, arg1) +} + +// mustEmbedUnimplementedMantleServiceServer mocks base method. +func (m *MockMantleServiceServer) mustEmbedUnimplementedMantleServiceServer() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "mustEmbedUnimplementedMantleServiceServer") +} + +// mustEmbedUnimplementedMantleServiceServer indicates an expected call of mustEmbedUnimplementedMantleServiceServer. +func (mr *MockMantleServiceServerMockRecorder) mustEmbedUnimplementedMantleServiceServer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "mustEmbedUnimplementedMantleServiceServer", reflect.TypeOf((*MockMantleServiceServer)(nil).mustEmbedUnimplementedMantleServiceServer)) +} + +// MockUnsafeMantleServiceServer is a mock of UnsafeMantleServiceServer interface. +type MockUnsafeMantleServiceServer struct { + ctrl *gomock.Controller + recorder *MockUnsafeMantleServiceServerMockRecorder +} + +// MockUnsafeMantleServiceServerMockRecorder is the mock recorder for MockUnsafeMantleServiceServer. +type MockUnsafeMantleServiceServerMockRecorder struct { + mock *MockUnsafeMantleServiceServer +} + +// NewMockUnsafeMantleServiceServer creates a new mock instance. +func NewMockUnsafeMantleServiceServer(ctrl *gomock.Controller) *MockUnsafeMantleServiceServer { + mock := &MockUnsafeMantleServiceServer{ctrl: ctrl} + mock.recorder = &MockUnsafeMantleServiceServerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUnsafeMantleServiceServer) EXPECT() *MockUnsafeMantleServiceServerMockRecorder { + return m.recorder +} + +// mustEmbedUnimplementedMantleServiceServer mocks base method. +func (m *MockUnsafeMantleServiceServer) mustEmbedUnimplementedMantleServiceServer() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "mustEmbedUnimplementedMantleServiceServer") +} + +// mustEmbedUnimplementedMantleServiceServer indicates an expected call of mustEmbedUnimplementedMantleServiceServer. +func (mr *MockUnsafeMantleServiceServerMockRecorder) mustEmbedUnimplementedMantleServiceServer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "mustEmbedUnimplementedMantleServiceServer", reflect.TypeOf((*MockUnsafeMantleServiceServer)(nil).mustEmbedUnimplementedMantleServiceServer)) +} From b1f8ee98592ac1814f5f7cdf5d10be27f511c80b Mon Sep 17 00:00:00 2001 From: Shinya Hayashi Date: Mon, 21 Oct 2024 10:39:46 +0000 Subject: [PATCH 2/4] recactor the primary role test Signed-off-by: Shinya Hayashi --- .../mantlebackup_controller_test.go | 176 ++++++------------ 1 file changed, 62 insertions(+), 114 deletions(-) diff --git a/internal/controller/mantlebackup_controller_test.go b/internal/controller/mantlebackup_controller_test.go index 489ce352..2ecebb3f 100644 --- a/internal/controller/mantlebackup_controller_test.go +++ b/internal/controller/mantlebackup_controller_test.go @@ -319,16 +319,21 @@ var _ = Describe("MantleBackup controller", func() { }) Context("when the role is `primary`", func() { + var mockCtrl *gomock.Controller + var grpcClient *proto.MockMantleServiceClient BeforeEach(func() { mgrUtil = testutil.NewManagerUtil(context.Background(), cfg, scheme.Scheme) + var t reporter + mockCtrl = gomock.NewController(t) + grpcClient = proto.NewMockMantleServiceClient(mockCtrl) reconciler = NewMantleBackupReconciler( mgrUtil.GetManager().GetClient(), mgrUtil.GetManager().GetScheme(), resMgr.ClusterID, RolePrimary, &PrimarySettings{ - Client: &mockGRPCClient{}, + Client: grpcClient, }, ) reconciler.ceph = testutil.NewFakeRBD() @@ -342,8 +347,50 @@ var _ = Describe("MantleBackup controller", func() { ns = resMgr.CreateNamespace() }) + AfterEach(func() { + if mockCtrl != nil { + mockCtrl.Finish() + } + }) It("should be synced to remote", func(ctx SpecContext) { + grpcClient.EXPECT().CreateOrUpdatePVC(gomock.Any(), gomock.Any()). + MinTimes(1).Return( + &proto.CreateOrUpdatePVCResponse{ + Uid: "a7c9d5e2-4b8f-4e2a-9d3f-1b6a7c8e9f2b", + }, nil) + var targetBackup mantlev1.MantleBackup + grpcClient.EXPECT().CreateOrUpdateMantleBackup(gomock.Any(), gomock.Any()). + MinTimes(1). + DoAndReturn( + func( + ctx context.Context, + req *proto.CreateOrUpdateMantleBackupRequest, + opts ...grpc.CallOption, + ) (*proto.CreateOrUpdateMantleBackupResponse, error) { + err := json.Unmarshal([]byte(req.GetMantleBackup()), &targetBackup) + if err != nil { + panic(err) + } + return &proto.CreateOrUpdateMantleBackupResponse{}, nil + }) + grpcClient.EXPECT().ListMantleBackup(gomock.Any(), gomock.Any()). + MinTimes(1). + DoAndReturn( + func( + ctx context.Context, + req *proto.ListMantleBackupRequest, + opts ...grpc.CallOption, + ) (*proto.ListMantleBackupResponse, error) { + data, err := json.Marshal([]mantlev1.MantleBackup{targetBackup}) + if err != nil { + panic(err) + } + return &proto.ListMantleBackupResponse{ + MantleBackupList: data, + }, nil + }) + pv, pvc, err := resMgr.CreateUniquePVAndPVC(ctx, ns) Expect(err).NotTo(HaveOccurred()) @@ -387,118 +434,19 @@ var _ = Describe("MantleBackup controller", func() { }) }) -type mockGRPCClient struct { - backup mantlev1.MantleBackup -} - -var _ proto.MantleServiceClient = (*mockGRPCClient)(nil) - -func (m *mockGRPCClient) CreateOrUpdatePVC( - ctx context.Context, - req *proto.CreateOrUpdatePVCRequest, - opts ...grpc.CallOption, -) (*proto.CreateOrUpdatePVCResponse, error) { - return &proto.CreateOrUpdatePVCResponse{}, nil -} - -func (m *mockGRPCClient) CreateOrUpdateMantleBackup( - ctx context.Context, - req *proto.CreateOrUpdateMantleBackupRequest, - opts ...grpc.CallOption, -) (*proto.CreateOrUpdateMantleBackupResponse, error) { - err := json.Unmarshal([]byte(req.MantleBackup), &m.backup) - if err != nil { - return nil, err - } - return &proto.CreateOrUpdateMantleBackupResponse{}, nil -} - -func (m *mockGRPCClient) ListMantleBackup( - ctx context.Context, - req *proto.ListMantleBackupRequest, - opts ...grpc.CallOption, -) (*proto.ListMantleBackupResponse, error) { - backups := []mantlev1.MantleBackup{m.backup} - data, err := json.Marshal(backups) - if err != nil { - return nil, err - } - return &proto.ListMantleBackupResponse{ - MantleBackupList: data, - }, nil -} - -func int2Ptr(i int) *int { - return &i -} - var _ = Describe("searchDiffOriginMantleBackup", func() { - testMantleBackup := mantlev1.MantleBackup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test0", - }, - Status: mantlev1.MantleBackupStatus{ - SnapID: int2Ptr(5), - }, - } + testMantleBackup := newMantleBackup("test0", "test-ns", nil, nil, false, + 5, metav1.ConditionTrue, metav1.ConditionFalse) basePrimaryBackups := []mantlev1.MantleBackup{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test1", - }, - Status: mantlev1.MantleBackupStatus{ - Conditions: []metav1.Condition{ - { - Type: mantlev1.BackupConditionReadyToUse, - Status: metav1.ConditionTrue, - }, - }, - SnapID: int2Ptr(1), - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test2", - }, - Status: mantlev1.MantleBackupStatus{ - Conditions: []metav1.Condition{ - { - Type: mantlev1.BackupConditionReadyToUse, - Status: metav1.ConditionTrue, - }, - }, - SnapID: int2Ptr(6), - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test3", - }, - Status: mantlev1.MantleBackupStatus{ - Conditions: []metav1.Condition{ - { - Type: mantlev1.BackupConditionReadyToUse, - Status: metav1.ConditionTrue, - }, - }, - SnapID: int2Ptr(3), - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test4", - }, - Status: mantlev1.MantleBackupStatus{ - Conditions: []metav1.Condition{ - { - Type: mantlev1.BackupConditionReadyToUse, - Status: metav1.ConditionTrue, - }, - }, - SnapID: int2Ptr(4), - }, - }, + *newMantleBackup("test1", "test-ns", nil, nil, false, + 1, metav1.ConditionTrue, metav1.ConditionTrue), + *newMantleBackup("test2", "test-ns", nil, nil, false, + 6, metav1.ConditionTrue, metav1.ConditionFalse), + *newMantleBackup("test3", "test-ns", nil, nil, false, + 3, metav1.ConditionTrue, metav1.ConditionTrue), + *newMantleBackup("test4", "test-ns", nil, nil, false, + 4, metav1.ConditionTrue, metav1.ConditionTrue), } // Note that slices.Clone() does the shallow copy. // ref. https://pkg.go.dev/slices#Clone @@ -537,16 +485,16 @@ var _ = Describe("searchDiffOriginMantleBackup", func() { } }, Entry("should return nil when no MantleBackup found on the secondary cluster", - &testMantleBackup, basePrimaryBackups, make(map[string]*mantlev1.MantleBackup), + testMantleBackup, basePrimaryBackups, make(map[string]*mantlev1.MantleBackup), false, ""), Entry("should find the correct MantleBackup", - &testMantleBackup, basePrimaryBackups, testSecondaryMantleBackups, + testMantleBackup, basePrimaryBackups, testSecondaryMantleBackups, true, "test3"), Entry("should skip the not-ready MantleBackup", - &testMantleBackup, primaryBackupsWithConditionFalse, testSecondaryMantleBackups, + testMantleBackup, primaryBackupsWithConditionFalse, testSecondaryMantleBackups, true, "test1"), Entry("should skip the MantleBackup with the deletion timestamp", - &testMantleBackup, primaryBackupsWithDeletionTimestamp, testSecondaryMantleBackups, + testMantleBackup, primaryBackupsWithDeletionTimestamp, testSecondaryMantleBackups, true, "test1"), ) }) From c9d18b4b5e7059264113183074aa09e38fa54189 Mon Sep 17 00:00:00 2001 From: Shinya Hayashi Date: Mon, 28 Oct 2024 01:36:27 +0000 Subject: [PATCH 3/4] go mod tidy Signed-off-by: Shinya Hayashi --- go.mod | 1 + 1 file changed, 1 insertion(+) diff --git a/go.mod b/go.mod index 90c4d22b..8807a601 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.8.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect From b2c986ba5a46047d14bbd32e41c4a545de2d8280 Mon Sep 17 00:00:00 2001 From: Shinya Hayashi Date: Mon, 28 Oct 2024 02:28:48 +0000 Subject: [PATCH 4/4] extend linter timeout Signed-off-by: Shinya Hayashi --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3f1f0e70..42ba63d1 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,7 @@ golangci-lint: .PHONY: lint lint: golangci-lint ## Run golangci-lint linter & yamllint - $(GOLANGCI_LINT) run + $(GOLANGCI_LINT) run --timeout 3m .PHONY: lint-fix lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes