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 }