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 }