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