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

Reconcile loop rework #54

Merged
merged 8 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions internal/controller/nab_init.go
mateusoliveira43 marked this conversation as resolved.
Show resolved Hide resolved
mpryc marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
Copyright 2024.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
"context"

"github.com/go-logr/logr"

nacv1alpha1 "github.com/migtools/oadp-non-admin/api/v1alpha1"
"github.com/migtools/oadp-non-admin/internal/common/constant"
"github.com/migtools/oadp-non-admin/internal/common/function"
)

// InitNonAdminBackup Initsets the New Phase on a NonAdminBackup object if it is not already set.
mpryc marked this conversation as resolved.
Show resolved Hide resolved
//
// Parameters:
//
// ctx: Context for the request.
// log: Logger instance for logging messages.
// nab: Pointer to the NonAdminBackup object.
//
// The function checks if the Phase of the NonAdminBackup object is empty.
// If it is empty, it sets the Phase to "New".
// It then returns boolean values indicating whether the reconciliation loop should requeue
// and whether the status was updated.
func (r *NonAdminBackupReconciler) InitNonAdminBackup(ctx context.Context, log logr.Logger, nab *nacv1alpha1.NonAdminBackup) (exitReconcile bool, requeueReconcile bool, errorReconcile error) {
logger := log.WithValues("NonAdminBackup", nab.Namespace)
mpryc marked this conversation as resolved.
Show resolved Hide resolved
// Set initial Phase
if nab.Status.Phase == constant.EmptyString {
// Phase: New
updatedStatus, errUpdate := function.UpdateNonAdminPhase(ctx, r.Client, logger, nab, nacv1alpha1.NonAdminBackupPhaseNew)
if updatedStatus {
logger.V(1).Info("NonAdminBackup CR - Rqueue after Phase Update")
mpryc marked this conversation as resolved.
Show resolved Hide resolved
return false, true, nil
}
if errUpdate != nil {
mpryc marked this conversation as resolved.
Show resolved Hide resolved
logger.Error(errUpdate, "Unable to set NonAdminBackup Phase: New", nameField, nab.Name, constant.NameSpaceString, nab.Namespace)
return true, false, errUpdate
}
}

return false, false, nil
}
90 changes: 90 additions & 0 deletions internal/controller/nab_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
Copyright 2024.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
"context"

"github.com/go-logr/logr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

nacv1alpha1 "github.com/migtools/oadp-non-admin/api/v1alpha1"
"github.com/migtools/oadp-non-admin/internal/common/constant"
"github.com/migtools/oadp-non-admin/internal/common/function"
)

// ValidateVeleroBackupSpec validates the VeleroBackup Spec from the NonAdminBackup.
mpryc marked this conversation as resolved.
Show resolved Hide resolved
//
// Parameters:
//
// ctx: Context for the request.
// log: Logger instance for logging messages.
// nab: Pointer to the NonAdminBackup object.
//
// The function attempts to get the BackupSpec from the NonAdminBackup object.
// If an error occurs during this process, the function sets the NonAdminBackup status to "BackingOff"
// and updates the corresponding condition accordingly.
// If the BackupSpec is invalid, the function sets the NonAdminBackup condition to "InvalidBackupSpec".
// If the BackupSpec is valid, the function sets the NonAdminBackup condition to "BackupAccepted".
func (r *NonAdminBackupReconciler) ValidateVeleroBackupSpec(ctx context.Context, log logr.Logger, nab *nacv1alpha1.NonAdminBackup) (exitReconcile bool, requeueReconcile bool, errorReconcile error) {
logger := log.WithValues("NonAdminBackup", nab.Namespace)

// Main Validation point for the VeleroBackup included in NonAdminBackup spec
_, err := function.GetBackupSpecFromNonAdminBackup(nab)

if err != nil {
errMsg := "NonAdminBackup CR does not contain valid BackupSpec"
logger.Error(err, errMsg)

updatedStatus, errUpdateStatus := function.UpdateNonAdminPhase(ctx, r.Client, logger, nab, nacv1alpha1.NonAdminBackupPhaseBackingOff)
if errUpdateStatus != nil {
logger.Error(errUpdateStatus, "Unable to set NonAdminBackup Phase: BackingOff", nameField, nab.Name, constant.NameSpaceString, nab.Namespace)
return true, false, errUpdateStatus
} else if updatedStatus {
mpryc marked this conversation as resolved.
Show resolved Hide resolved
// We do not requeue - the State was set to BackingOff
return true, false, nil
}

// Continue. VeleroBackup looks fine, setting Accepted condition
updatedCondition, errUpdateCondition := function.UpdateNonAdminBackupCondition(ctx, r.Client, logger, nab, nacv1alpha1.NonAdminConditionAccepted, metav1.ConditionFalse, "InvalidBackupSpec", errMsg)
if updatedCondition {
// We do not requeue - this was only Condition update
return true, false, nil
}

if errUpdateCondition != nil {
mpryc marked this conversation as resolved.
Show resolved Hide resolved
logger.Error(errUpdateCondition, "Unable to set BackupAccepted Condition: False", nameField, nab.Name, constant.NameSpaceString, nab.Namespace)
return true, false, errUpdateCondition
}
// We do not requeue - this was error from getting Spec from NAB
return true, false, err
}

updatedStatus, errUpdateStatus := function.UpdateNonAdminBackupCondition(ctx, r.Client, logger, nab, nacv1alpha1.NonAdminConditionAccepted, metav1.ConditionTrue, "BackupAccepted", "backup accepted")
if updatedStatus {
// We do requeue - The VeleroBackup got accepted and next reconcile loop will continue
// with further work on the VeleroBackup such as creating it
return false, true, nil
}

if errUpdateStatus != nil {
mpryc marked this conversation as resolved.
Show resolved Hide resolved
logger.Error(errUpdateStatus, "Unable to set BackupAccepted Condition: True", nameField, nab.Name, constant.NameSpaceString, nab.Namespace)
return true, false, errUpdateStatus
}

return false, false, nil
}
136 changes: 136 additions & 0 deletions internal/controller/nab_velero_backupspec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
Copyright 2024.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
"context"
"errors"

"github.com/go-logr/logr"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

nacv1alpha1 "github.com/migtools/oadp-non-admin/api/v1alpha1"
"github.com/migtools/oadp-non-admin/internal/common/constant"
"github.com/migtools/oadp-non-admin/internal/common/function"
)

// CreateVeleroBackupSpec creates or updates a Velero Backup object based on the provided NonAdminBackup object.
//
// Parameters:
//
// ctx: Context for the request.
// log: Logger instance for logging messages.
// nab: Pointer to the NonAdminBackup object.
//
// The function generates a 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.
// The function returns boolean values indicating whether the reconciliation loop should exit or requeue
func (r *NonAdminBackupReconciler) CreateVeleroBackupSpec(ctx context.Context, log logr.Logger, nab *nacv1alpha1.NonAdminBackup) (exitReconcile bool, requeueReconcile bool, errorReconcile error) {
logger := log.WithValues("NonAdminBackup", nab.Namespace)

veleroBackupName := function.GenerateVeleroBackupName(nab.Namespace, nab.Name)

if veleroBackupName == constant.EmptyString {
return true, false, errors.New("unable to generate Velero Backup name")
}

veleroBackup := velerov1api.Backup{}
err := r.Get(ctx, client.ObjectKey{Namespace: constant.OadpNamespace, Name: veleroBackupName}, &veleroBackup)

if err != nil && apierrors.IsNotFound(err) {
// Create VeleroBackup
// Don't update phase nor conditions yet.
// Those will be updated when then Reconcile loop is triggered by the VeleroBackup object
logger.Info("No backup found", nameField, veleroBackupName)

// We don't validate error here.
// This was already validated in the ValidateVeleroBackupSpec
backupSpec, errBackup := function.GetBackupSpecFromNonAdminBackup(nab)

if errBackup != nil {
// Should never happen as it was already checked
return true, false, errBackup
}

veleroBackup = velerov1api.Backup{
ObjectMeta: metav1.ObjectMeta{
Name: veleroBackupName,
Namespace: constant.OadpNamespace,
},
Spec: *backupSpec,
}
} else if err != nil && !apierrors.IsNotFound(err) {
logger.Error(err, "Unable to fetch VeleroBackup")
return true, false, err
} else {
// We should not update already created VeleroBackup object.
mateusoliveira43 marked this conversation as resolved.
Show resolved Hide resolved
// 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
}

// Ensure labels are set for the Backup object
existingLabels := veleroBackup.Labels
naManagedLabels := function.AddNonAdminLabels(existingLabels)
veleroBackup.Labels = naManagedLabels

// Ensure annotations are set for the Backup object
existingAnnotations := veleroBackup.Annotations
ownerUUID := string(nab.ObjectMeta.UID)
nabManagedAnnotations := function.AddNonAdminBackupAnnotations(nab.Namespace, nab.Name, ownerUUID, existingAnnotations)
veleroBackup.Annotations = nabManagedAnnotations

_, err = controllerutil.CreateOrPatch(ctx, r.Client, &veleroBackup, nil)
if err != nil {
logger.Error(err, "Failed to create backup", nameField, veleroBackupName)
return true, false, err
}
logger.Info("VeleroBackup successfully created", nameField, veleroBackupName)

_, errUpdate := function.UpdateNonAdminPhase(ctx, r.Client, logger, nab, nacv1alpha1.NonAdminBackupPhaseCreated)
if errUpdate != nil {
logger.Error(errUpdate, "Unable to set NonAdminBackup Phase: Created", nameField, nab.Name, constant.NameSpaceString, nab.Namespace)
return true, false, errUpdate
}
_, errUpdate = function.UpdateNonAdminBackupCondition(ctx, r.Client, logger, nab, nacv1alpha1.NonAdminConditionAccepted, metav1.ConditionTrue, "Validated", "Valid Backup config")
if errUpdate != nil {
logger.Error(errUpdate, "Unable to set BackupAccepted Condition: True", nameField, nab.Name, constant.NameSpaceString, nab.Namespace)
return true, false, errUpdate
}
_, errUpdate = function.UpdateNonAdminBackupCondition(ctx, r.Client, logger, nab, nacv1alpha1.NonAdminConditionQueued, metav1.ConditionTrue, "BackupScheduled", "Created Velero Backup object")
if errUpdate != nil {
logger.Error(errUpdate, "Unable to set BackupQueued Condition: True", nameField, nab.Name, constant.NameSpaceString, nab.Namespace)
return true, false, errUpdate
}

return false, false, nil
}
81 changes: 81 additions & 0 deletions internal/controller/nac_reconcile_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
Copyright 2024.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import "errors"

// ReconcileFunc represents a function that performs a reconciliation operation.
// Parameters:
// - ...any: A variadic number of arguments,
// allowing for flexibility in passing parameters to the reconciliation function.
//
// Returns:
// - bool: Indicates whether the reconciliation process should exit.
// - bool: Indicates whether the reconciliation process should requeue.
// - error: An error encountered during reconciliation, if any.
type ReconcileFunc func(...any) (bool, bool, error)
mpryc marked this conversation as resolved.
Show resolved Hide resolved

// ReconcileBatch executes a batch of reconcile functions sequentially,
// allowing for complex reconciliation processes in a controlled manner.
// It iterates through the provided reconcile functions until one of the
// following conditions is met:
// - A function signals the need to exit the reconciliation process.
// - A function signals the need to exit and requeue the reconciliation process.
// - An error occurs during reconciliation.
//
// If any reconcile function indicates a need to exit or requeue,
// the function immediately returns with the respective exit and requeue
// flags set. If an error occurs during any reconciliation function call,
// it is propagated up and returned. If none of the reconcile functions
// indicate a need to exit, requeue, or result in an error, the function
// returns false for both exit and requeue flags, indicating successful reconciliation.
//
// If a reconcile function signals both the need to exit and requeue, indicating
// conflicting signals, the function returns an error with the exit flag set to true
// and the requeue flag set to false, so no .
//
// Parameters:
// - reconcileFuncs: A list of ReconcileFunc functions representing
// the reconciliation steps to be executed sequentially.
//
// Returns:
// - bool: Indicates whether the reconciliation process should exit.
// - bool: Indicates whether the reconciliation process should requeue.
// - error: An error encountered during reconciliation, if any.
func ReconcileBatch(reconcileFuncs ...ReconcileFunc) (exitReconcile bool, requeueReconcile bool, errorReconcile error) {
var exit, requeue bool
var err error
var conflictError = errors.New("conflicting exit and requeue signals - can not be both true")

for _, f := range reconcileFuncs {
exit, requeue, err = f()

// Check if both exit and requeue are true
// If this happens do not requeue, but exit with error
if exit && requeue {
return true, false, conflictError
}

// Return if there is a need to exit, requeue, or an error occurred
if exit || requeue || err != nil {
return exit, requeue, err
}
}

// Do not requeue or exit reconcile. Also no error occurred.
return false, false, nil
}
Loading
Loading