github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/clusters/multiclusterapplicationconfiguration/controller.go (about)

     1  // Copyright (c) 2021, 2022, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package multiclusterapplicationconfiguration
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  
    10  	"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
    11  	"github.com/verrazzano/verrazzano/application-operator/constants"
    12  	"github.com/verrazzano/verrazzano/application-operator/controllers/clusters"
    13  	vzconst "github.com/verrazzano/verrazzano/pkg/constants"
    14  	vzlogInit "github.com/verrazzano/verrazzano/pkg/log"
    15  	vzlog2 "github.com/verrazzano/verrazzano/pkg/log/vzlog"
    16  	"go.uber.org/zap"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  	"k8s.io/apimachinery/pkg/types"
    19  	ctrl "sigs.k8s.io/controller-runtime"
    20  	"sigs.k8s.io/controller-runtime/pkg/client"
    21  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    22  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    23  
    24  	clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1"
    25  )
    26  
    27  // Reconciler reconciles a MultiClusterApplicationConfiguration resource. It fetches the embedded
    28  // OAM ApplicationConfiguration, mutates it based on the MultiClusterApplicationConfiguration, and
    29  // updates the status of the MultiClusterApplicationConfiguration to reflect the success or
    30  // failure of the changes to the embedded resource
    31  type Reconciler struct {
    32  	client.Client
    33  	Log          *zap.SugaredLogger
    34  	Scheme       *runtime.Scheme
    35  	AgentChannel chan clusters.StatusUpdateMessage
    36  }
    37  
    38  const (
    39  	finalizerName  = "multiclusterapplicationconfiguration.verrazzano.io"
    40  	controllerName = "multiclusterappconfiguration"
    41  )
    42  
    43  // Reconcile reconciles a MultiClusterApplicationConfiguration resource. It fetches the embedded OAM
    44  // app config, mutates it based on the MultiClusterApplicationConfiguration, and updates the status
    45  // of the MultiClusterApplicationConfiguration to reflect the success or failure of the changes to
    46  // the embedded resource
    47  func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    48  	if ctx == nil {
    49  		return ctrl.Result{}, errors.New("context cannot be nil")
    50  	}
    51  
    52  	// We do not want any resource to get reconciled if it is in namespace kube-system
    53  	// This is due to a bug found in OKE, it should not affect functionality of any vz operators
    54  	// If this is the case then return success
    55  	if req.Namespace == vzconst.KubeSystem {
    56  		log := zap.S().With(vzlogInit.FieldResourceNamespace, req.Namespace, vzlogInit.FieldResourceName, req.Name, vzlogInit.FieldController, controllerName)
    57  		log.Infof("Multi-cluster application configuration resource %v should not be reconciled in kube-system namespace, ignoring", req.NamespacedName)
    58  		return reconcile.Result{}, nil
    59  	}
    60  
    61  	var mcAppConfig clustersv1alpha1.MultiClusterApplicationConfiguration
    62  	err := r.fetchMultiClusterAppConfig(ctx, req.NamespacedName, &mcAppConfig)
    63  	if err != nil {
    64  		return clusters.IgnoreNotFoundWithLog(err, zap.S())
    65  	}
    66  	log, err := clusters.GetResourceLogger("mcapplicationconfiguration", req.NamespacedName, &mcAppConfig)
    67  	if err != nil {
    68  		zap.S().Errorf("Failed to create controller logger for multi-cluster application configuration resource: %v", err)
    69  		return clusters.NewRequeueWithDelay(), nil
    70  	}
    71  	log.Oncef("Reconciling multi-cluster application configuration resource %v, generation %v", req.NamespacedName, mcAppConfig.Generation)
    72  
    73  	res, err := r.doReconcile(ctx, mcAppConfig, log)
    74  	if clusters.ShouldRequeue(res) {
    75  		return res, nil
    76  	}
    77  	// Never return an error since it has already been logged and we don't want the
    78  	// controller runtime to log again (with stack trace).  Just re-queue if there is an error.
    79  	if err != nil {
    80  		return clusters.NewRequeueWithDelay(), nil
    81  	}
    82  	log.Oncef("Finished reconciling multi-cluster application configuration %v", req.NamespacedName)
    83  
    84  	return ctrl.Result{}, nil
    85  }
    86  
    87  // doReconcile performs the reconciliation operations for the MC application configuration
    88  func (r *Reconciler) doReconcile(ctx context.Context, mcAppConfig clustersv1alpha1.MultiClusterApplicationConfiguration, log vzlog2.VerrazzanoLogger) (ctrl.Result, error) {
    89  	if !mcAppConfig.ObjectMeta.DeletionTimestamp.IsZero() {
    90  		// delete the wrapped resource since MC is being deleted
    91  		err := clusters.DeleteAssociatedResource(ctx, r.Client, &mcAppConfig, finalizerName, &v1alpha2.ApplicationConfiguration{}, types.NamespacedName{Namespace: mcAppConfig.Namespace, Name: mcAppConfig.Name})
    92  		if err != nil {
    93  			log.Errorf("Failed to delete associated app config and finalizer: %v", err)
    94  		}
    95  		return reconcile.Result{}, err
    96  	}
    97  
    98  	oldState := clusters.SetEffectiveStateIfChanged(mcAppConfig.Spec.Placement, &mcAppConfig.Status)
    99  	if !clusters.IsPlacedInThisCluster(ctx, r, mcAppConfig.Spec.Placement) {
   100  		if oldState != mcAppConfig.Status.State {
   101  			// This must be done whether the resource is placed in this cluster or not, because we
   102  			// could be in an admin cluster and receive cluster level statuses from managed clusters,
   103  			// which can change our effective state
   104  			err := r.Status().Update(ctx, &mcAppConfig)
   105  			if err != nil {
   106  				return ctrl.Result{}, err
   107  			}
   108  		}
   109  		// if this mc app config is no longer placed on this cluster, remove the associated app config
   110  		err := clusters.DeleteAssociatedResource(ctx, r.Client, &mcAppConfig, finalizerName, &v1alpha2.ApplicationConfiguration{}, types.NamespacedName{Namespace: mcAppConfig.Namespace, Name: mcAppConfig.Name})
   111  		return ctrl.Result{}, err
   112  	}
   113  
   114  	log.Debug("MultiClusterApplicationConfiguration create or update with underlying OAM applicationconfiguration",
   115  		"applicationconfiguration", mcAppConfig.Spec.Template.Metadata.Name,
   116  		"placement", mcAppConfig.Spec.Placement.Clusters[0].Name)
   117  	opResult, err := r.createOrUpdateAppConfig(ctx, mcAppConfig)
   118  
   119  	// Add our finalizer if not already added
   120  	if err == nil {
   121  		_, err = clusters.AddFinalizer(ctx, r.Client, &mcAppConfig, finalizerName)
   122  	}
   123  
   124  	ctrlResult, updateErr := r.updateStatus(ctx, &mcAppConfig, opResult, err)
   125  
   126  	// if an error occurred in createOrUpdate, return that error with a requeue
   127  	// even if update status succeeded
   128  	if err != nil {
   129  		res := ctrl.Result{Requeue: true, RequeueAfter: clusters.GetRandomRequeueDelay()}
   130  		return res, err
   131  	}
   132  
   133  	return ctrlResult, updateErr
   134  }
   135  
   136  // SetupWithManager registers our controller with the manager
   137  func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
   138  	return ctrl.NewControllerManagedBy(mgr).
   139  		For(&clustersv1alpha1.MultiClusterApplicationConfiguration{}).
   140  		Complete(r)
   141  }
   142  
   143  func (r *Reconciler) fetchMultiClusterAppConfig(ctx context.Context, name types.NamespacedName, mcAppConfig *clustersv1alpha1.MultiClusterApplicationConfiguration) error {
   144  	return r.Get(ctx, name, mcAppConfig)
   145  }
   146  
   147  func (r *Reconciler) createOrUpdateAppConfig(ctx context.Context, mcAppConfig clustersv1alpha1.MultiClusterApplicationConfiguration) (controllerutil.OperationResult, error) {
   148  	var oamAppConfig v1alpha2.ApplicationConfiguration
   149  	oamAppConfig.Namespace = mcAppConfig.Namespace
   150  	oamAppConfig.Name = mcAppConfig.Name
   151  
   152  	return controllerutil.CreateOrUpdate(ctx, r.Client, &oamAppConfig, func() error {
   153  		r.mutateAppConfig(mcAppConfig, &oamAppConfig)
   154  		return nil
   155  	})
   156  }
   157  
   158  // mutateAppConfig mutates the OAM app config to reflect the contents of the parent
   159  // MultiClusterApplicationConfiguration
   160  func (r *Reconciler) mutateAppConfig(mcAppConfig clustersv1alpha1.MultiClusterApplicationConfiguration, oamAppConfig *v1alpha2.ApplicationConfiguration) {
   161  	oamAppConfig.Spec = mcAppConfig.Spec.Template.Spec
   162  	oamAppConfig.Labels = mcAppConfig.Spec.Template.Metadata.Labels
   163  	// Mark the app config we unwrapped with verrazzano-managed=true, to distinguish from
   164  	// those that the user might have created directly
   165  	if oamAppConfig.Labels == nil {
   166  		oamAppConfig.Labels = map[string]string{}
   167  	}
   168  	oamAppConfig.Labels[vzconst.VerrazzanoManagedLabelKey] = constants.LabelVerrazzanoManagedDefault
   169  	oamAppConfig.Annotations = mcAppConfig.Spec.Template.Metadata.Annotations
   170  }
   171  
   172  func (r *Reconciler) updateStatus(ctx context.Context, mcAppConfig *clustersv1alpha1.MultiClusterApplicationConfiguration, opResult controllerutil.OperationResult, err error) (ctrl.Result, error) {
   173  	clusterName := clusters.GetClusterName(ctx, r.Client)
   174  	newCondition := clusters.GetConditionFromResult(err, opResult, "OAM Application Configuration")
   175  	updateFunc := func() error { return r.Status().Update(ctx, mcAppConfig) }
   176  	return clusters.UpdateStatus(mcAppConfig, &mcAppConfig.Status, mcAppConfig.Spec.Placement, newCondition, clusterName,
   177  		r.AgentChannel, updateFunc)
   178  }