Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add integration tests for NAB #73

Open
wants to merge 16 commits into
base: master
Choose a base branch
from

Conversation

mateusoliveira43
Copy link
Contributor

@mateusoliveira43 mateusoliveira43 commented Aug 28, 2024

Why the changes were made

Add Integration tests for NAB, so we can trust our code base more

Fixes #61

Also

  • removed duplication in logs
  • removed unnecessary validations
  • made each reconcile only execute one update call per object, to avoid race conditions
  • removed terminal error from wrong places

Can help on refactor of #62

How to test the changes made

To run integration tests (and also unit tests), run make simulation-test

Check Test logs to see what is being tested

To check code coverage, run go tool cover -html=cover.out

Also check #73 (comment)

Copy link

openshift-ci bot commented Aug 28, 2024

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

Copy link

openshift-ci bot commented Aug 28, 2024

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: mateusoliveira43

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
go.mod Outdated Show resolved Hide resolved
Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Copy link
Contributor Author

@mateusoliveira43 mateusoliveira43 Sep 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest running these test against master branch to see the current problems

To do so, in master branch run

git restore -s fix/add-integration-tests-nab hack/extra-crds/velero.io_backups.yaml
git restore -s fix/add-integration-tests-nab internal/controller/suite_test.go
git restore -s fix/add-integration-tests-nab internal/controller/nonadminbackup_controller_test.go
git restore -s fix/add-integration-tests-nab cmd/main.go
git restore -s fix/add-integration-tests-nab Makefile 
git apply test-master.diff
make simulation-test

where test-master.diff is

diff --git a/internal/common/function/function_test.go b/internal/common/function/function_test.go
index dcec107..840c4ce 100644
--- a/internal/common/function/function_test.go
+++ b/internal/common/function/function_test.go
@@ -22,6 +22,7 @@ import (
 	"reflect"
 	"testing"
 
+	"github.com/onsi/ginkgo/v2"
 	"github.com/stretchr/testify/assert"
 	velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -35,6 +36,8 @@ import (
 	"github.com/migtools/oadp-non-admin/internal/common/constant"
 )
 
+var _ = ginkgo.Describe("PLACEHOLDER", func() {})
+
 func TestMergeMaps(t *testing.T) {
 	const (
 		d     = "d"
diff --git a/internal/controller/nonadminbackup_controller.go b/internal/controller/nonadminbackup_controller.go
index 85456c2..d05590b 100644
--- a/internal/controller/nonadminbackup_controller.go
+++ b/internal/controller/nonadminbackup_controller.go
@@ -20,7 +20,6 @@ package controller
 import (
 	"context"
 	"errors"
-	"time"
 
 	"github.com/go-logr/logr"
 	velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
@@ -43,8 +42,9 @@ import (
 // NonAdminBackupReconciler reconciles a NonAdminBackup object
 type NonAdminBackupReconciler struct {
 	client.Client
-	Scheme  *runtime.Scheme
-	Context context.Context
+	Scheme        *runtime.Scheme
+	Context       context.Context
+	OADPNamespace string
 }
 
 const (
@@ -89,7 +89,7 @@ func (r *NonAdminBackupReconciler) Reconcile(ctx context.Context, req ctrl.Reque
 
 	reconcileExit, reconcileRequeue, reconcileErr := r.InitNonAdminBackup(ctx, rLog, &nab)
 	if reconcileRequeue {
-		return ctrl.Result{Requeue: true, RequeueAfter: requeueTimeSeconds * time.Second}, reconcileErr
+		return ctrl.Result{Requeue: true}, reconcileErr
 	} else if reconcileExit && reconcileErr != nil {
 		return ctrl.Result{}, reconcile.TerminalError(reconcileErr)
 	} else if reconcileExit {
@@ -98,7 +98,7 @@ func (r *NonAdminBackupReconciler) Reconcile(ctx context.Context, req ctrl.Reque
 
 	reconcileExit, reconcileRequeue, reconcileErr = r.ValidateVeleroBackupSpec(ctx, rLog, &nab)
 	if reconcileRequeue {
-		return ctrl.Result{Requeue: true, RequeueAfter: requeueTimeSeconds * time.Second}, reconcileErr
+		return ctrl.Result{Requeue: true}, reconcileErr
 	} else if reconcileExit && reconcileErr != nil {
 		return ctrl.Result{}, reconcile.TerminalError(reconcileErr)
 	} else if reconcileExit {
@@ -107,7 +107,7 @@ func (r *NonAdminBackupReconciler) Reconcile(ctx context.Context, req ctrl.Reque
 
 	reconcileExit, reconcileRequeue, reconcileErr = r.CreateVeleroBackupSpec(ctx, rLog, &nab)
 	if reconcileRequeue {
-		return ctrl.Result{Requeue: true, RequeueAfter: requeueTimeSeconds * time.Second}, reconcileErr
+		return ctrl.Result{Requeue: true}, reconcileErr
 	} else if reconcileExit && reconcileErr != nil {
 		return ctrl.Result{}, reconcile.TerminalError(reconcileErr)
 	} else if reconcileExit {
@@ -235,7 +235,7 @@ func (r *NonAdminBackupReconciler) CreateVeleroBackupSpec(ctx context.Context, l
 	}
 
 	veleroBackup := velerov1api.Backup{}
-	err := r.Get(ctx, client.ObjectKey{Namespace: constant.OadpNamespace, Name: veleroBackupName}, &veleroBackup)
+	err := r.Get(ctx, client.ObjectKey{Namespace: r.OADPNamespace, Name: veleroBackupName}, &veleroBackup)
 
 	if err != nil && apierrors.IsNotFound(err) {
 		// Create VeleroBackup
@@ -255,7 +255,7 @@ func (r *NonAdminBackupReconciler) CreateVeleroBackupSpec(ctx context.Context, l
 		veleroBackup = velerov1api.Backup{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      veleroBackupName,
-				Namespace: constant.OadpNamespace,
+				Namespace: r.OADPNamespace,
 			},
 			Spec: *backupSpec,
 		}
@@ -325,7 +325,7 @@ func (r *NonAdminBackupReconciler) SetupWithManager(mgr ctrl.Manager) error {
 		WithEventFilter(predicate.CompositePredicate{
 			NonAdminBackupPredicate: predicate.NonAdminBackupPredicate{},
 			VeleroBackupPredicate: predicate.VeleroBackupPredicate{
-				OadpVeleroNamespace: constant.OadpNamespace,
+				OadpVeleroNamespace: r.OADPNamespace,
 			},
 			Context: r.Context,
 		}).

Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
internal/common/function/function.go Show resolved Hide resolved
internal/controller/nonadminbackup_controller.go Outdated Show resolved Hide resolved
// It then checks if a Velero Backup object with that name already exists. If it does not exist, it creates a new one.
// The function generates the name for the Velero Backup object based on the provided namespace and name.
// It then checks if a Velero Backup object with that name already exists. If it does not exist, it creates a new one
// and updates NonAdminBackup Status. Otherwise, updates NonAdminBackup VeleroBackup Spec and Status based on Velero Backup object Spec and Status.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not update the NAB Velerospec from Velero backup object spec here, its fine if Velero adds some default values, its not the responsibility of the controller to update spec, specs are user defined or by external entity, controller's job is to get the object to the desired state and provide updates on status in doing so. The spec update to NAB object will also generate an event to trigger reconcile which we don't want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discuss in meeting

// The function returns boolean values indicating whether the reconciliation loop should exit or requeue
func (r *NonAdminBackupReconciler) CreateVeleroBackupSpec(ctx context.Context, logrLogger logr.Logger, nab *nacv1alpha1.NonAdminBackup) (exitReconcile bool, requeueReconcile bool, errorReconcile error) {
logger := logrLogger.WithValues("CreateVeleroBackupSpec", nab.Namespace)
func (r *NonAdminBackupReconciler) UpdateSpecStatus(ctx context.Context, logrLogger logr.Logger, nab *nacv1alpha1.NonAdminBackup) (exitReconcile bool, requeueReconcile bool, errorReconcile error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets changes the function name to some thing like ProcessNABSpec or something similar

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original function name was CreateVeleroBackupSpec which I think was correct. It reflected we are creating it not updating nor Processing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It did update NAB when VeleroBackup updates

} else {
// We should not update already created VeleroBackup object.
// The VeleroBackup within NonAdminBackup will
// be reverted back to the previous state - the state which created VeleroBackup
// in a first place, so they will be in sync.
logger.Info("Backup already exists, updating NonAdminBackup status", nameField, veleroBackupName)
updatedNab, errBackupUpdate := function.UpdateNonAdminBackupFromVeleroBackup(ctx, r.Client, logger, nab, &veleroBackup)
// Regardless if the status was updated or not, we should not
// requeue here as it was only status update.
if errBackupUpdate != nil {
return true, false, errBackupUpdate
} else if updatedNab {
logger.V(1).Info("NonAdminBackup CR - Rqueue after Status Update")
return false, true, nil
}
return true, false, nil
}

we should break this function into smaller ones, it does too many things. But I tried to changed as little as possible in this PR

I suggest follow up with break up of function. Do you agree?

internal/predicate/velerobackup_predicate.go Outdated Show resolved Hide resolved
@shubham-pampattiwar
Copy link
Member

OK at the high level this is starting to look like:

  • We have a main Reconcile function for NAB controller
  • This controller has a bunch of tasks: Init, ValidateSpec and so on...
  • Now each task returns exitReconcile bool, requeueReconcile bool, errorReconcile error
    IMO as I had said before too we could structure this similar to the OADP DPA reconciler
    It wont be the exact same but refactoring would be similar.
    In our NAB controller case, in addition to what the controller tasks currently return exitReconcile bool, requeueReconcile bool, errorReconcile error, we could add one more struct called NABUpdate This struct would consist of the phase and condition that needs to be updated for NAB object in that particular reconcile loop and the status update call could be executed at the end of the reconcile loop in a consolidated manner.
    All the above changes would simplify the code workflow a lot, would be easier to understand/debug and make the code maintainable as well.

Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
internal/controller/nonadminbackup_controller_test.go Outdated Show resolved Hide resolved
gomega.Expect(deleteTestNamespaces(ctx, nonAdminNamespaceName, oadpNamespaceName)).To(gomega.Succeed())
})

ginkgo.DescribeTable("Reconcile called by NonAdminBackup Delete event",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets change this test describe, we can say some thing like "NAB object does not exist case"

},
result: reconcile.Result{Requeue: true},
}),
ginkgo.Entry("When called by Requeue(update NonAdminBackup phase to new), should update NonAdminBackup Condition to Accepted True and Requeue", nonAdminBackupSingleReconcileScenario{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reconcile when triggred by Requeue(NonAdminBackup phase to new), should update NonAdminBackup Condition to Accepted True and Requeue

},
result: reconcile.Result{Requeue: true},
}),
ginkgo.Entry("When called by Requeue(update NonAdminBackup Condition to Accepted True), should update NonAdminBackup phase to created and Requeue", nonAdminBackupSingleReconcileScenario{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reconcile triggered by Requeue(NonAdminBackup status has phase New and Condition as Accepted True), should update NonAdminBackup phase to created and Requeue

},
result: reconcile.Result{Requeue: true},
}),
ginkgo.Entry("When called by Requeue(update NonAdminBackup phase to created), should update NonAdminBackup Condition to Queued True and Requeue", nonAdminBackupSingleReconcileScenario{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reconcile triggered by Requeue (NonAdminBackup Status has phase created and Accepted condition as true), should add another condition of Queued as True and Requeue

},
result: reconcile.Result{Requeue: true},
}),
ginkgo.Entry("When called by Requeue(update NonAdminBackup Condition to Queued True), should update NonAdminBackup VeleroBackupStatus and Requeue", nonAdminBackupSingleReconcileScenario{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is failing when executed independently/focused. (For Velero Backup name counter seems incorrect, expects a 6th reconcile instead of first) I am assuming these tests should always be executed together ?
image
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I fixed the problem in next commit, please test when possible

},
result: reconcile.Result{Requeue: true},
}),
ginkgo.Entry("When called by Requeue(update NonAdminBackup Condition to Queued True), should update NonAdminBackup VeleroBackupStatus and Requeue", nonAdminBackupSingleReconcileScenario{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reconcile triggered by Requeue(NonAdminBackup has Condition to Queued True), should update NonAdminBackup VeleroBackupStatus with Velero Backup details and Requeue

},
result: reconcile.Result{Requeue: true},
}),
ginkgo.Entry("When called by Requeue(update NonAdminBackup VeleroBackupStatus), should update NonAdminBackup spec BackupSpec and Requeue", nonAdminBackupSingleReconcileScenario{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets hold off on adding this test as we are gonna remove this task from NAB reconcile loop.

gomega.Expect(checkTestNonAdminBackupStatus(nonAdminBackup, scenario.status)).To(gomega.Succeed())
if scenario.priorStatus != nil {
if len(scenario.priorStatus.VeleroBackupName) > 0 {
gomega.Expect(reflect.DeepEqual(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something incomplete or residual/debug code ?
We are comparing the NAB.spec.backupSpec with VeleroBackupSpec with just IncludedNS. Let me know if am missing something here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to confirm NAB spec was updated by reconcile loop

Signed-off-by: Mateus Oliveira <msouzaol@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Ready for Review
Development

Successfully merging this pull request may close these issues.

Create tests for NAB controller.
4 participants