Skip to content

Commit

Permalink
Merge pull request #198 from Missxiaoguo/validate
Browse files Browse the repository at this point in the history
Add IBU spec validation for OADP before prep
  • Loading branch information
openshift-merge-bot[bot] authored Jan 2, 2024
2 parents a06cde4 + 6ae98e8 commit bd3d014
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 2 deletions.
41 changes: 40 additions & 1 deletion controllers/ibu_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,20 @@ func (r *ImageBasedUpgradeReconciler) Reconcile(ctx context.Context, req ctrl.Re
return
}

// Update in progress condition to true and idle condition to false when transitioning to non idle stage
if validateStageTransition(ibu, isAfterPivot) {
// Update in progress condition to true and idle condition to false when transitioning to non idle stage
// Validate the IBU spec if the transition is to prep stage
if ibu.Spec.Stage == lcav1alpha1.Stages.Prep {
var isValid bool
isValid, err = r.validateIBUSpec(ctx, ibu)
if err != nil {
return
}
if !isValid {
err = r.updateStatus(ctx, ibu)
return
}
}
nextReconcile, err = r.handleStage(ctx, ibu, ibu.Spec.Stage)
if err != nil {
return
Expand Down Expand Up @@ -375,6 +387,33 @@ func validateStageTransition(ibu *lcav1alpha1.ImageBasedUpgrade, isAfterPivot bo
return true
}

// validateIBUSpec validates the IBU CR, returns true if the spec is valid, false otherwise
func (r *ImageBasedUpgradeReconciler) validateIBUSpec(ctx context.Context, ibu *lcav1alpha1.ImageBasedUpgrade) (bool, error) {
r.Log.Info("Validating IBU spec")

// If OADP configmap is provided, validate the configmap and check if OADP operator is available
if len(ibu.Spec.OADPContent) != 0 {
err := r.BackupRestore.ValidateOadpConfigmap(ctx, ibu.Spec.OADPContent)
if err != nil {
if backuprestore.IsBRFailedValidationError(err) {
utils.SetPrepStatusFailed(ibu, err.Error())
return false, nil
}
return false, err
}

err = r.BackupRestore.CheckOadpOperatorAvailability(ctx)
if err != nil {
if backuprestore.IsBRFailedValidationError(err) {
utils.SetPrepStatusFailed(ibu, err.Error())
return false, nil
}
return false, err
}
}
return true, nil
}

func (r *ImageBasedUpgradeReconciler) updateStatus(ctx context.Context, ibu *lcav1alpha1.ImageBasedUpgrade) error {
ibu.Status.ObservedGeneration = ibu.ObjectMeta.Generation

Expand Down
98 changes: 97 additions & 1 deletion internal/backuprestore/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ var (
// BackuperRestorer interface also used for mocks
type BackuperRestorer interface {
CleanupBackups(ctx context.Context) (bool, error)
CheckOadpOperatorAvailability(ctx context.Context) error
DeleteOadpOperator(ctx context.Context, namespace string) error
ExportOadpConfigurationToDir(ctx context.Context, toDir, oadpNamespace string) error
ExportRestoresToDir(ctx context.Context, configMaps []lcav1alpha1.ConfigMapRef, toDir string) error
Expand All @@ -86,6 +87,7 @@ type BackuperRestorer interface {
RestoreOadpConfigurations(ctx context.Context) error
StartOrTrackBackup(ctx context.Context, backups []*velerov1.Backup) (*BackupTracker, error)
StartOrTrackRestore(ctx context.Context, restores []*velerov1.Restore) (*RestoreTracker, error)
ValidateOadpConfigmap(ctx context.Context, content []lcav1alpha1.ConfigMapRef) error
}

// BRHandler handles the backup and restore
Expand Down Expand Up @@ -160,7 +162,7 @@ func IsBRFailedError(err error) bool {
func IsBRFailedValidationError(err error) bool {
var brErr *BRStatusError
if errors.As(err, &brErr) {
if brErr.Type == "Backup" || brErr.Type == "Restore" {
if brErr.Type == "Backup" || brErr.Type == "Restore" || brErr.Type == "OADP" {
return brErr.Reason == "FailedValidation"
}
}
Expand Down Expand Up @@ -308,3 +310,97 @@ func isDPAReconciled(dpa *unstructured.Unstructured) bool {
}
return false
}

func (h *BRHandler) ValidateOadpConfigmap(ctx context.Context, content []lcav1alpha1.ConfigMapRef) error {
configmaps, err := common.GetConfigMaps(ctx, h.Client, content)
if err != nil {
if k8serrors.IsNotFound(err) {
errMsg := fmt.Sprintf("OADP configmap not found, error: %s. Please create the configmap.", err.Error())
h.Log.Error(nil, errMsg)
return NewBRFailedValidationError("OADP", errMsg)
}
return err
}

backups, err := h.extractBackupFromConfigmaps(ctx, configmaps)
if err != nil {
return err
}
restores, err := h.extractRestoreFromConfigmaps(ctx, configmaps)
if err != nil {
return err
}

if len(backups) == 0 || len(restores) == 0 || len(backups) != len(restores) {
errMsg := "Both backup and restore CRs should be specified in OADP configmaps and each backup CR should be paired with a corresponding restore CR."
h.Log.Error(nil, errMsg)
return NewBRFailedValidationError("OADP", errMsg)
}

// Check if the backup CRs defined in restore CRs exist in OADP configmaps
for _, restore := range restores {
found := false
for _, backup := range backups {
if restore.Spec.BackupName == backup.Name {
found = true
break
}
}
if !found {
errMsg := fmt.Sprintf("The backup CR %s defined in restore CR %s not found in OADP configmaps", restore.Spec.BackupName, restore.Name)
h.Log.Error(nil, errMsg)
return NewBRFailedValidationError("OADP", errMsg)
}
}
return nil
}

func (h *BRHandler) CheckOadpOperatorAvailability(ctx context.Context) error {
// Check if OADP is running
oadpCsv := &operatorsv1alpha1.ClusterServiceVersionList{}
if err := h.List(ctx, oadpCsv, &client.ListOptions{Namespace: OadpNs}); err != nil {
return err
}

if len(oadpCsv.Items) == 0 ||
!(oadpCsv.Items[0].Status.Phase == operatorsv1alpha1.CSVPhaseSucceeded && oadpCsv.Items[0].Status.Reason == operatorsv1alpha1.CSVReasonInstallSuccessful) {
errMsg := fmt.Sprintf("Please ensure OADP operator is running successfully in the %s", OadpNs)
h.Log.Error(nil, errMsg)
return NewBRFailedValidationError("OADP", errMsg)
}

// Check if OADP DPA is reconciled
dpaList := &unstructured.UnstructuredList{}
dpaList.SetGroupVersionKind(dpaGvkList)
opts := []client.ListOption{
client.InNamespace(OadpNs),
}
if err := h.List(ctx, dpaList, opts...); err != nil {
return err
}

if len(dpaList.Items) == 0 {
errMsg := fmt.Sprintf("No DataProtectionApplication CR found in the %s", OadpNs)
h.Log.Error(nil, errMsg)
return NewBRFailedValidationError("OADP", errMsg)
}

if len(dpaList.Items) != 1 {
errMsg := fmt.Sprintf("Only one DataProtectionApplication CR is allowed in the %s,", OadpNs)
h.Log.Error(nil, errMsg)
return NewBRFailedValidationError("OADP", errMsg)
}

if !isDPAReconciled(&dpaList.Items[0]) {
errMsg := fmt.Sprintf("DataProtectionApplication CR %s is not reconciled", dpaList.Items[0].GetName())
h.Log.Error(nil, errMsg)
return NewBRFailedValidationError("OADP", errMsg)
}

// Check if the storage backend is ready
err := h.ensureStorageBackendAvailable(ctx, OadpNs)
if err != nil {
return NewBRFailedValidationError("OADP", err.Error())
}
return nil
}
28 changes: 28 additions & 0 deletions internal/backuprestore/mocks/mock_backuprestore.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit bd3d014

Please sign in to comment.