github.com/oam-dev/kubevela@v1.9.11/pkg/controller/core.oam.dev/v1beta1/application/application_controller.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package application
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  	"time"
    24  
    25  	"github.com/crossplane/crossplane-runtime/pkg/event"
    26  	"github.com/crossplane/crossplane-runtime/pkg/meta"
    27  	"github.com/pkg/errors"
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/api/equality"
    30  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/runtime"
    33  	"k8s.io/apiserver/pkg/util/feature"
    34  	"k8s.io/klog/v2"
    35  	"k8s.io/utils/strings/slices"
    36  	ctrl "sigs.k8s.io/controller-runtime"
    37  	"sigs.k8s.io/controller-runtime/pkg/client"
    38  	"sigs.k8s.io/controller-runtime/pkg/controller"
    39  	ctrlEvent "sigs.k8s.io/controller-runtime/pkg/event"
    40  	ctrlHandler "sigs.k8s.io/controller-runtime/pkg/handler"
    41  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    42  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    43  	"sigs.k8s.io/controller-runtime/pkg/source"
    44  
    45  	monitorContext "github.com/kubevela/pkg/monitor/context"
    46  	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
    47  	wfContext "github.com/kubevela/workflow/pkg/context"
    48  	"github.com/kubevela/workflow/pkg/cue/packages"
    49  	"github.com/kubevela/workflow/pkg/executor"
    50  	wffeatures "github.com/kubevela/workflow/pkg/features"
    51  
    52  	ctrlrec "github.com/kubevela/pkg/controller/reconciler"
    53  
    54  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    55  	"github.com/oam-dev/kubevela/apis/core.oam.dev/condition"
    56  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    57  	velatypes "github.com/oam-dev/kubevela/apis/types"
    58  	"github.com/oam-dev/kubevela/pkg/appfile"
    59  	"github.com/oam-dev/kubevela/pkg/auth"
    60  	common2 "github.com/oam-dev/kubevela/pkg/controller/common"
    61  	core "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
    62  	"github.com/oam-dev/kubevela/pkg/features"
    63  	"github.com/oam-dev/kubevela/pkg/monitor/metrics"
    64  	"github.com/oam-dev/kubevela/pkg/oam"
    65  	oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
    66  	"github.com/oam-dev/kubevela/pkg/resourcekeeper"
    67  	"github.com/oam-dev/kubevela/pkg/resourcetracker"
    68  	"github.com/oam-dev/kubevela/pkg/workflow"
    69  	"github.com/oam-dev/kubevela/version"
    70  )
    71  
    72  const (
    73  	errUpdateApplicationFinalizer = "cannot update application finalizer"
    74  )
    75  
    76  const (
    77  	// baseWorkflowBackoffWaitTime is the time to wait gc check
    78  	baseGCBackoffWaitTime = 3000 * time.Millisecond
    79  )
    80  
    81  var (
    82  	// EnableResourceTrackerDeleteOnlyTrigger optimize ResourceTracker mutate event trigger by only receiving deleting events
    83  	EnableResourceTrackerDeleteOnlyTrigger = true
    84  )
    85  
    86  // Reconciler reconciles an Application object
    87  type Reconciler struct {
    88  	client.Client
    89  	pd       *packages.PackageDiscover
    90  	Scheme   *runtime.Scheme
    91  	Recorder event.Recorder
    92  	options
    93  }
    94  
    95  type options struct {
    96  	appRevisionLimit     int
    97  	concurrentReconciles int
    98  	ignoreAppNoCtrlReq   bool
    99  	controllerVersion    string
   100  }
   101  
   102  // +kubebuilder:rbac:groups=core.oam.dev,resources=applications,verbs=get;list;watch;create;update;patch;delete
   103  // +kubebuilder:rbac:groups=core.oam.dev,resources=applications/status,verbs=get;update;patch
   104  
   105  // Reconcile process app event
   106  // nolint:gocyclo
   107  func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
   108  	ctx, cancel := ctrlrec.NewReconcileContext(ctx)
   109  	defer cancel()
   110  	logCtx := monitorContext.NewTraceContext(ctx, "").AddTag("application", req.String(), "controller", "application")
   111  	logCtx.Info("Start reconcile application")
   112  	defer logCtx.Commit("End reconcile application")
   113  	app := new(v1beta1.Application)
   114  	if err := r.Get(ctx, client.ObjectKey{
   115  		Name:      req.Name,
   116  		Namespace: req.Namespace,
   117  	}, app); err != nil {
   118  		if !kerrors.IsNotFound(err) {
   119  			logCtx.Error(err, "get application")
   120  		}
   121  		return r.result(client.IgnoreNotFound(err)).ret()
   122  	}
   123  	ctx = withOriginalApp(ctx, app)
   124  	if ctrlrec.IsPaused(app) {
   125  		return ctrl.Result{}, nil
   126  	}
   127  
   128  	if !r.matchControllerRequirement(app) {
   129  		logCtx.Info("skip app: not match the controller requirement of app")
   130  		return ctrl.Result{}, nil
   131  	}
   132  
   133  	timeReporter := timeReconcile(app)
   134  	defer timeReporter()
   135  
   136  	logCtx.AddTag("resource_version", app.ResourceVersion).AddTag("generation", app.Generation)
   137  	ctx = oamutil.SetNamespaceInCtx(ctx, app.Namespace)
   138  	logCtx.SetContext(ctx)
   139  	setVelaVersion(app)
   140  	logCtx.AddTag("publish_version", app.GetAnnotations()[oam.AnnotationPublishVersion])
   141  
   142  	appParser := appfile.NewApplicationParser(r.Client, r.pd)
   143  	handler, err := NewAppHandler(logCtx, r, app)
   144  	if err != nil {
   145  		return r.endWithNegativeCondition(logCtx, app, condition.ReconcileError(err), common.ApplicationStarting)
   146  	}
   147  	endReconcile, result, err := r.handleFinalizers(logCtx, app, handler)
   148  	if err != nil {
   149  		if app.GetDeletionTimestamp() == nil {
   150  			return r.endWithNegativeCondition(logCtx, app, condition.ReconcileError(err), common.ApplicationStarting)
   151  		}
   152  		return result, err
   153  	}
   154  	if endReconcile {
   155  		return result, nil
   156  	}
   157  
   158  	appFile, err := appParser.GenerateAppFile(logCtx, app)
   159  	if err != nil {
   160  		r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedParse, err))
   161  		return r.endWithNegativeCondition(logCtx, app, condition.ErrorCondition("Parsed", err), common.ApplicationRendering)
   162  	}
   163  	app.Status.SetConditions(condition.ReadyCondition("Parsed"))
   164  	r.Recorder.Event(app, event.Normal(velatypes.ReasonParsed, velatypes.MessageParsed))
   165  
   166  	if err := handler.PrepareCurrentAppRevision(logCtx, appFile); err != nil {
   167  		logCtx.Error(err, "Failed to prepare app revision")
   168  		r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedRevision, err))
   169  		return r.endWithNegativeCondition(logCtx, app, condition.ErrorCondition("Revision", err), common.ApplicationRendering)
   170  	}
   171  	if err := handler.FinalizeAndApplyAppRevision(logCtx); err != nil {
   172  		logCtx.Error(err, "Failed to apply app revision")
   173  		r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedRevision, err))
   174  		return r.endWithNegativeCondition(logCtx, app, condition.ErrorCondition("Revision", err), common.ApplicationRendering)
   175  	}
   176  	logCtx.Info("Successfully prepare current app revision", "revisionName", handler.currentAppRev.Name,
   177  		"revisionHash", handler.currentRevHash, "isNewRevision", handler.isNewRevision)
   178  	app.Status.SetConditions(condition.ReadyCondition("Revision"))
   179  	r.Recorder.Event(app, event.Normal(velatypes.ReasonRevisoned, velatypes.MessageRevisioned))
   180  
   181  	if err := handler.UpdateAppLatestRevisionStatus(logCtx, r.patchStatus); err != nil {
   182  		logCtx.Error(err, "Failed to update application status")
   183  		return r.endWithNegativeCondition(logCtx, app, condition.ReconcileError(err), common.ApplicationRendering)
   184  	}
   185  	logCtx.Info("Successfully apply application revision")
   186  
   187  	if err := handler.ApplyPolicies(logCtx, appFile); err != nil {
   188  		logCtx.Error(err, "[handle ApplyPolicies]")
   189  		r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedApply, err))
   190  		return r.endWithNegativeCondition(logCtx, app, condition.ErrorCondition(common.PolicyCondition.String(), errors.WithMessage(err, "ApplyPolices")), common.ApplicationPolicyGenerating)
   191  	}
   192  	app.Status.SetConditions(condition.ReadyCondition(common.PolicyCondition.String()))
   193  	r.Recorder.Event(app, event.Normal(velatypes.ReasonPolicyGenerated, velatypes.MessagePolicyGenerated))
   194  
   195  	handler.CheckWorkflowRestart(logCtx, app)
   196  
   197  	workflowInstance, runners, err := handler.GenerateApplicationSteps(logCtx, app, appParser, appFile)
   198  	if err != nil {
   199  		logCtx.Error(err, "[handle workflow]")
   200  		r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedWorkflow, err))
   201  		return r.endWithNegativeCondition(logCtx, app, condition.ErrorCondition(common.WorkflowCondition.String(), err), common.ApplicationRendering)
   202  	}
   203  	app.Status.SetConditions(condition.ReadyCondition(common.RenderCondition.String()))
   204  	r.Recorder.Event(app, event.Normal(velatypes.ReasonRendered, velatypes.MessageRendered))
   205  
   206  	workflowExecutor := executor.New(workflowInstance, r.Client, nil)
   207  	authCtx := logCtx.Fork("execute application workflow")
   208  	defer authCtx.Commit("finish execute application workflow")
   209  	authCtx = auth.MonitorContextWithUserInfo(authCtx, app)
   210  	tBeginWorkflowExecution := time.Now()
   211  	workflowState, err := workflowExecutor.ExecuteRunners(authCtx, runners)
   212  	metrics.AppReconcileStageDurationHistogram.WithLabelValues("execute-workflow").Observe(time.Since(tBeginWorkflowExecution).Seconds())
   213  	if err != nil {
   214  		logCtx.Error(err, "[handle workflow]")
   215  		r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedWorkflow, err))
   216  		return r.endWithNegativeCondition(logCtx, app, condition.ErrorCondition(common.WorkflowCondition.String(), err), common.ApplicationRunningWorkflow)
   217  	}
   218  
   219  	handler.addServiceStatus(false, app.Status.Services...)
   220  	handler.addAppliedResource(true, app.Status.AppliedResources...)
   221  	app.Status.AppliedResources = handler.appliedResources
   222  	app.Status.Services = handler.services
   223  	workflowUpdated := app.Status.Workflow.Message != "" && workflowInstance.Status.Message == ""
   224  	workflowInstance.Status.Phase = workflowState
   225  	app.Status.Workflow = workflow.ConvertWorkflowStatus(workflowInstance.Status, app.Status.Workflow.AppRevision)
   226  	logCtx.Info(fmt.Sprintf("Workflow return state=%s", workflowState))
   227  	switch workflowState {
   228  	case workflowv1alpha1.WorkflowStateSuspending:
   229  		if duration := workflowExecutor.GetSuspendBackoffWaitTime(); duration > 0 {
   230  			_, err = r.gcResourceTrackers(logCtx, handler, common.ApplicationWorkflowSuspending, false, workflowUpdated)
   231  			return r.result(err).requeue(duration).ret()
   232  		}
   233  		if !workflow.IsFailedAfterRetry(app) || !feature.DefaultMutableFeatureGate.Enabled(wffeatures.EnableSuspendOnFailure) {
   234  			r.stateKeep(logCtx, handler, app)
   235  		}
   236  		return r.gcResourceTrackers(logCtx, handler, common.ApplicationWorkflowSuspending, false, workflowUpdated)
   237  	case workflowv1alpha1.WorkflowStateTerminated:
   238  		if workflowInstance.Status.EndTime.IsZero() {
   239  			r.doWorkflowFinish(logCtx, app, handler, workflowState)
   240  		}
   241  		return r.gcResourceTrackers(logCtx, handler, common.ApplicationWorkflowTerminated, false, workflowUpdated)
   242  	case workflowv1alpha1.WorkflowStateFailed:
   243  		if workflowInstance.Status.EndTime.IsZero() {
   244  			r.doWorkflowFinish(logCtx, app, handler, workflowState)
   245  		}
   246  		return r.gcResourceTrackers(logCtx, handler, common.ApplicationWorkflowFailed, false, workflowUpdated)
   247  	case workflowv1alpha1.WorkflowStateExecuting:
   248  		_, err = r.gcResourceTrackers(logCtx, handler, common.ApplicationRunningWorkflow, false, workflowUpdated)
   249  		return r.result(err).requeue(workflowExecutor.GetBackoffWaitTime()).ret()
   250  	case workflowv1alpha1.WorkflowStateSucceeded:
   251  		if workflowInstance.Status.EndTime.IsZero() {
   252  			r.doWorkflowFinish(logCtx, app, handler, workflowState)
   253  		}
   254  	case workflowv1alpha1.WorkflowStateSkipped:
   255  		return r.result(nil).requeue(workflowExecutor.GetBackoffWaitTime()).ret()
   256  	default:
   257  	}
   258  
   259  	var phase = common.ApplicationRunning
   260  	if !hasHealthCheckPolicy(appFile.ParsedPolicies) {
   261  		app.Status.Services = handler.services
   262  		if !isHealthy(handler.services) {
   263  			phase = common.ApplicationUnhealthy
   264  		}
   265  	}
   266  
   267  	r.stateKeep(logCtx, handler, app)
   268  
   269  	opts := []resourcekeeper.GCOption{
   270  		resourcekeeper.AppRevisionLimitGCOption(r.appRevisionLimit),
   271  	}
   272  	if DisableAllApplicationRevision {
   273  		opts = append(opts, resourcekeeper.DisableApplicationRevisionGCOption{})
   274  	}
   275  	if DisableAllComponentRevision {
   276  		opts = append(opts, resourcekeeper.DisableGCComponentRevisionOption{})
   277  	}
   278  
   279  	if _, _, err := handler.resourceKeeper.GarbageCollect(logCtx, opts...); err != nil {
   280  		logCtx.Error(err, "Failed to run garbage collection")
   281  		r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedGC, err))
   282  		return r.endWithNegativeCondition(logCtx, app, condition.ReconcileError(err), phase)
   283  	}
   284  	logCtx.Info("Successfully garbage collect")
   285  	app.Status.SetConditions(condition.Condition{
   286  		Type:               condition.ConditionType(common.ReadyCondition.String()),
   287  		Status:             corev1.ConditionTrue,
   288  		LastTransitionTime: metav1.Now(),
   289  		Reason:             condition.ReasonReconcileSuccess,
   290  	})
   291  	r.Recorder.Event(app, event.Normal(velatypes.ReasonDeployed, velatypes.MessageDeployed))
   292  	return r.gcResourceTrackers(logCtx, handler, phase, true, false)
   293  }
   294  
   295  func (r *Reconciler) stateKeep(logCtx monitorContext.Context, handler *AppHandler, app *v1beta1.Application) {
   296  	if feature.DefaultMutableFeatureGate.Enabled(features.ApplyOnce) {
   297  		return
   298  	}
   299  	t := time.Now()
   300  	defer func() {
   301  		metrics.AppReconcileStageDurationHistogram.WithLabelValues("state-keep").Observe(time.Since(t).Seconds())
   302  	}()
   303  	if err := handler.resourceKeeper.StateKeep(logCtx); err != nil {
   304  		logCtx.Error(err, "Failed to run prevent-configuration-drift")
   305  		r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedStateKeep, err))
   306  		app.Status.SetConditions(condition.ErrorCondition("StateKeep", err))
   307  	}
   308  }
   309  
   310  func (r *Reconciler) gcResourceTrackers(logCtx monitorContext.Context, handler *AppHandler, phase common.ApplicationPhase, gcOutdated bool, isUpdate bool) (ctrl.Result, error) {
   311  	subCtx := logCtx.Fork("gc_resourceTrackers", monitorContext.DurationMetric(func(v float64) {
   312  		metrics.AppReconcileStageDurationHistogram.WithLabelValues("gc-rt").Observe(v)
   313  	}))
   314  	defer subCtx.Commit("finish gc resourceTrackers")
   315  
   316  	statusUpdater := r.patchStatus
   317  	if isUpdate {
   318  		statusUpdater = r.updateStatus
   319  	}
   320  
   321  	options := []resourcekeeper.GCOption{
   322  		resourcekeeper.AppRevisionLimitGCOption(r.appRevisionLimit),
   323  	}
   324  	if DisableAllApplicationRevision {
   325  		options = append(options, resourcekeeper.DisableApplicationRevisionGCOption{})
   326  	}
   327  	if DisableAllComponentRevision {
   328  		options = append(options, resourcekeeper.DisableGCComponentRevisionOption{})
   329  	}
   330  	if !gcOutdated {
   331  		options = append(options,
   332  			resourcekeeper.DisableMarkStageGCOption{},
   333  			resourcekeeper.DisableGCComponentRevisionOption{},
   334  			resourcekeeper.DisableLegacyGCOption{},
   335  			resourcekeeper.DisableApplicationRevisionGCOption{},
   336  		)
   337  	}
   338  
   339  	finished, waiting, err := handler.resourceKeeper.GarbageCollect(resourcekeeper.WithPhase(logCtx, phase), options...)
   340  	if err != nil {
   341  		logCtx.Error(err, "Failed to gc resourcetrackers")
   342  		cond := condition.Deleting()
   343  		cond.Message = fmt.Sprintf("error encountered during garbage collection: %s", err.Error())
   344  		handler.app.Status.SetConditions(cond)
   345  		return r.result(statusUpdater(logCtx, handler.app, phase)).ret()
   346  	}
   347  	if !finished {
   348  		logCtx.Info("GarbageCollecting resourcetrackers unfinished")
   349  		cond := condition.Deleting()
   350  		if len(waiting) > 0 {
   351  			cond.Message = fmt.Sprintf("Waiting for %s to delete. (At least %d resources are deleting.)", waiting[0].DisplayName(), len(waiting))
   352  		}
   353  		handler.app.Status.SetConditions(cond)
   354  		return r.result(statusUpdater(logCtx, handler.app, phase)).requeue(baseGCBackoffWaitTime).ret()
   355  	}
   356  	logCtx.Info("GarbageCollected resourcetrackers")
   357  	return r.result(statusUpdater(logCtx, handler.app, phase)).ret()
   358  }
   359  
   360  type reconcileResult struct {
   361  	time.Duration
   362  	err error
   363  }
   364  
   365  func (r *reconcileResult) requeue(d time.Duration) *reconcileResult {
   366  	r.Duration = d
   367  	return r
   368  }
   369  
   370  func (r *reconcileResult) ret() (ctrl.Result, error) {
   371  	if r.Duration.Seconds() != 0 {
   372  		return ctrl.Result{RequeueAfter: r.Duration}, r.err
   373  	} else if r.err != nil {
   374  		return ctrl.Result{}, r.err
   375  	}
   376  	return ctrl.Result{RequeueAfter: common2.ApplicationReSyncPeriod}, nil
   377  }
   378  
   379  func (r *reconcileResult) end(endReconcile bool) (bool, ctrl.Result, error) {
   380  	ret, err := r.ret()
   381  	return endReconcile, ret, err
   382  }
   383  
   384  func (r *Reconciler) result(err error) *reconcileResult {
   385  	return &reconcileResult{err: err}
   386  }
   387  
   388  // NOTE Because resource tracker is cluster-scoped resources, we cannot garbage collect them
   389  // by setting application(namespace-scoped) as their owners.
   390  // We must delete all resource trackers related to an application through finalizer logic.
   391  func (r *Reconciler) handleFinalizers(ctx monitorContext.Context, app *v1beta1.Application, handler *AppHandler) (bool, ctrl.Result, error) {
   392  	if app.ObjectMeta.DeletionTimestamp.IsZero() {
   393  		if !meta.FinalizerExists(app, oam.FinalizerResourceTracker) {
   394  			subCtx := ctx.Fork("handle-finalizers", monitorContext.DurationMetric(func(v float64) {
   395  				metrics.AppReconcileStageDurationHistogram.WithLabelValues("add-finalizer").Observe(v)
   396  			}))
   397  			defer subCtx.Commit("finish add finalizers")
   398  			meta.AddFinalizer(app, oam.FinalizerResourceTracker)
   399  			subCtx.Info("Register new finalizer for application", "finalizer", oam.FinalizerResourceTracker)
   400  			return r.result(errors.Wrap(r.Client.Update(ctx, app), errUpdateApplicationFinalizer)).end(true)
   401  		}
   402  	} else {
   403  		if slices.Contains(app.GetFinalizers(), oam.FinalizerResourceTracker) {
   404  			subCtx := ctx.Fork("handle-finalizers", monitorContext.DurationMetric(func(v float64) {
   405  				metrics.AppReconcileStageDurationHistogram.WithLabelValues("remove-finalizer").Observe(v)
   406  			}))
   407  			defer subCtx.Commit("finish remove finalizers")
   408  			rootRT, currentRT, historyRTs, crRT, err := resourcetracker.ListApplicationResourceTrackers(ctx, r.Client, app)
   409  			if err != nil {
   410  				return r.result(err).end(true)
   411  			}
   412  			result, err := r.gcResourceTrackers(ctx, handler, common.ApplicationDeleting, true, true)
   413  			if err != nil {
   414  				return true, result, err
   415  			}
   416  			if rootRT == nil && currentRT == nil && len(historyRTs) == 0 && crRT == nil {
   417  				if revs, err := resourcekeeper.ListApplicationRevisions(ctx, r.Client, app.Name, app.Namespace); len(revs) > 0 || err != nil {
   418  					klog.Infof("garbage collecting application revisions for application %s/%s, rest: %d, err: %s", app.Namespace, app.Name, len(revs), err)
   419  					return r.result(err).requeue(baseGCBackoffWaitTime).end(true)
   420  				}
   421  				meta.RemoveFinalizer(app, oam.FinalizerResourceTracker)
   422  				meta.RemoveFinalizer(app, oam.FinalizerOrphanResource)
   423  				return r.result(errors.Wrap(r.Client.Update(ctx, app), errUpdateApplicationFinalizer)).end(true)
   424  			}
   425  			if wfContext.EnableInMemoryContext {
   426  				wfContext.MemStore.DeleteInMemoryContext(app.Name)
   427  			}
   428  			return true, result, err
   429  		}
   430  	}
   431  	return r.result(nil).end(false)
   432  }
   433  
   434  func (r *Reconciler) endWithNegativeCondition(ctx context.Context, app *v1beta1.Application, condition condition.Condition, phase common.ApplicationPhase) (ctrl.Result, error) {
   435  	app.SetConditions(condition)
   436  	if err := r.patchStatus(ctx, app, phase); err != nil {
   437  		return r.result(errors.WithMessage(err, "cannot update application status")).ret()
   438  	}
   439  	return r.result(fmt.Errorf("object level reconcile error, type: %q, msg: %q", string(condition.Type), condition.Message)).ret()
   440  }
   441  
   442  // Application status can be updated by two methods: patch and update.
   443  type method int
   444  
   445  const (
   446  	patch = iota
   447  	update
   448  )
   449  
   450  type statusPatcher func(ctx context.Context, app *v1beta1.Application, phase common.ApplicationPhase) error
   451  
   452  func (r *Reconciler) patchStatus(ctx context.Context, app *v1beta1.Application, phase common.ApplicationPhase) error {
   453  	return r.writeStatusByMethod(ctx, patch, app, phase)
   454  }
   455  
   456  func (r *Reconciler) updateStatus(ctx context.Context, app *v1beta1.Application, phase common.ApplicationPhase) error {
   457  	return r.writeStatusByMethod(ctx, update, app, phase)
   458  }
   459  
   460  func (r *Reconciler) writeStatusByMethod(ctx context.Context, method method, app *v1beta1.Application, phase common.ApplicationPhase) error {
   461  	// pre-check if the status is changed
   462  	app.Status.Phase = phase
   463  	updateObservedGeneration(app)
   464  	if oldApp, ok := originalAppFrom(ctx); ok && oldApp != nil && equality.Semantic.DeepEqual(oldApp.Status, app.Status) {
   465  		return nil
   466  	}
   467  	ctx, cancel := ctrlrec.NewReconcileTerminationContext(ctx)
   468  	defer cancel()
   469  	var f func() error
   470  	switch method {
   471  	case patch:
   472  		f = func() error { return r.Status().Patch(ctx, app, client.Merge) }
   473  	case update:
   474  		f = func() error { return r.Status().Update(ctx, app) }
   475  	default:
   476  		// Should never happen
   477  		panic("unknown method")
   478  	}
   479  	if err := f(); err != nil {
   480  		executor.StepStatusCache.Store(fmt.Sprintf("%s-%s", app.Name, app.Namespace), -1)
   481  		return err
   482  	}
   483  	return nil
   484  }
   485  
   486  func (r *Reconciler) doWorkflowFinish(logCtx monitorContext.Context, app *v1beta1.Application, handler *AppHandler, state workflowv1alpha1.WorkflowRunPhase) {
   487  	logCtx = logCtx.Fork("do-workflow-finish", monitorContext.DurationMetric(func(v float64) {
   488  		metrics.AppReconcileStageDurationHistogram.WithLabelValues("do-workflow-finish").Observe(v)
   489  	}))
   490  	defer logCtx.Commit("do-workflow-finish")
   491  	app.Status.Workflow.Finished = true
   492  	app.Status.Workflow.EndTime = metav1.Now()
   493  	executor.StepStatusCache.Delete(fmt.Sprintf("%s-%s", app.Name, app.Namespace))
   494  	wfContext.CleanupMemoryStore(app.Name, app.Namespace)
   495  	t := time.Since(app.Status.Workflow.StartTime.Time).Seconds()
   496  	metrics.WorkflowFinishedTimeHistogram.WithLabelValues(string(state)).Observe(t)
   497  	if state == workflowv1alpha1.WorkflowStateSucceeded {
   498  		app.Status.SetConditions(condition.ReadyCondition(common.WorkflowCondition.String()))
   499  		r.Recorder.Event(app, event.Normal(velatypes.ReasonApplied, velatypes.MessageWorkflowFinished))
   500  	}
   501  	handler.UpdateApplicationRevisionStatus(logCtx, handler.currentAppRev, app.Status.Workflow)
   502  	logCtx.Info("Application manifests has applied by workflow successfully")
   503  }
   504  
   505  func hasHealthCheckPolicy(policies []*appfile.Component) bool {
   506  	for _, p := range policies {
   507  		if p.FullTemplate != nil && p.FullTemplate.PolicyDefinition != nil &&
   508  			p.FullTemplate.PolicyDefinition.Spec.ManageHealthCheck {
   509  			return true
   510  		}
   511  	}
   512  	return false
   513  }
   514  
   515  func isHealthy(services []common.ApplicationComponentStatus) bool {
   516  	for _, service := range services {
   517  		if !service.Healthy {
   518  			return false
   519  		}
   520  		for _, tr := range service.Traits {
   521  			if !tr.Healthy {
   522  				return false
   523  			}
   524  		}
   525  	}
   526  	return true
   527  }
   528  
   529  // SetupWithManager install to manager
   530  func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
   531  	return ctrl.NewControllerManagedBy(mgr).
   532  		Watches(&source.Kind{
   533  			Type: &v1beta1.ResourceTracker{},
   534  		}, ctrlHandler.EnqueueRequestsFromMapFunc(findObjectForResourceTracker)).
   535  		WithOptions(controller.Options{
   536  			MaxConcurrentReconciles: r.concurrentReconciles,
   537  		}).
   538  		WithEventFilter(predicate.Funcs{
   539  			// filter the changes in workflow status
   540  			// let workflow handle its reconcile
   541  			UpdateFunc: func(e ctrlEvent.UpdateEvent) bool {
   542  				newApp, isNewApp := e.ObjectNew.DeepCopyObject().(*v1beta1.Application)
   543  				old, isOldApp := e.ObjectOld.DeepCopyObject().(*v1beta1.Application)
   544  				if !isNewApp || !isOldApp {
   545  					return filterManagedFieldChangesUpdate(e)
   546  				}
   547  
   548  				// We think this event is triggered by resync
   549  				if reflect.DeepEqual(old, newApp) {
   550  					return true
   551  				}
   552  
   553  				// filter managedFields changes
   554  				old.ManagedFields = nil
   555  				newApp.ManagedFields = nil
   556  
   557  				// if the generation is changed, return true to let the controller handle it
   558  				if old.Generation != newApp.Generation {
   559  					return true
   560  				}
   561  
   562  				// filter the events triggered by initial application status
   563  				if newApp.Status.Phase == common.ApplicationRendering || (old.Status.Phase == common.ApplicationRendering && newApp.Status.Phase == common.ApplicationRunningWorkflow) {
   564  					return false
   565  				}
   566  
   567  				// ignore the changes in workflow status
   568  				if old.Status.Workflow != nil && newApp.Status.Workflow != nil {
   569  					// only workflow execution will change the status.workflow
   570  					// let workflow backoff to requeue the event
   571  					newApp.Status.Workflow.Steps = old.Status.Workflow.Steps
   572  					newApp.Status.Workflow.ContextBackend = old.Status.Workflow.ContextBackend
   573  					newApp.Status.Workflow.Message = old.Status.Workflow.Message
   574  					newApp.Status.Workflow.EndTime = old.Status.Workflow.EndTime
   575  				}
   576  
   577  				// appliedResources and Services will be changed during the execution of workflow
   578  				// once the resources is added, the managed fields will also be changed
   579  				newApp.Status.AppliedResources = old.Status.AppliedResources
   580  				newApp.Status.Services = old.Status.Services
   581  				// the resource version will be changed if the object is changed
   582  				// ignore this change and let reflect.DeepEqual to compare the rest of the object
   583  				newApp.ResourceVersion = old.ResourceVersion
   584  				return !reflect.DeepEqual(old, newApp)
   585  			},
   586  			CreateFunc: func(e ctrlEvent.CreateEvent) bool {
   587  				return true
   588  			},
   589  			DeleteFunc: func(e ctrlEvent.DeleteEvent) bool {
   590  				return true
   591  			},
   592  		}).
   593  		For(&v1beta1.Application{}).
   594  		Complete(r)
   595  }
   596  
   597  // Setup adds a controller that reconciles App.
   598  func Setup(mgr ctrl.Manager, args core.Args) error {
   599  	reconciler := Reconciler{
   600  		Client:   mgr.GetClient(),
   601  		Scheme:   mgr.GetScheme(),
   602  		Recorder: event.NewAPIRecorder(mgr.GetEventRecorderFor("Application")),
   603  		pd:       args.PackageDiscover,
   604  		options:  parseOptions(args),
   605  	}
   606  	return reconciler.SetupWithManager(mgr)
   607  }
   608  
   609  func updateObservedGeneration(app *v1beta1.Application) {
   610  	if app.Status.ObservedGeneration != app.Generation {
   611  		app.Status.ObservedGeneration = app.Generation
   612  	}
   613  }
   614  
   615  // filterManagedFieldChangesUpdate filter resourceTracker update event by ignoring managedFields changes
   616  // For old k8s version like 1.18.5, the managedField could always update and cause infinite loop
   617  // this function helps filter those events and prevent infinite loop
   618  func filterManagedFieldChangesUpdate(e ctrlEvent.UpdateEvent) bool {
   619  	newTracker, isNewRT := e.ObjectNew.DeepCopyObject().(*v1beta1.ResourceTracker)
   620  	old, isOldRT := e.ObjectOld.DeepCopyObject().(*v1beta1.ResourceTracker)
   621  	if !isNewRT || !isOldRT {
   622  		return true
   623  	}
   624  	newTracker.ManagedFields = old.ManagedFields
   625  	newTracker.ResourceVersion = old.ResourceVersion
   626  	return !reflect.DeepEqual(newTracker, old)
   627  }
   628  
   629  func findObjectForResourceTracker(rt client.Object) []reconcile.Request {
   630  	if EnableResourceTrackerDeleteOnlyTrigger && rt.GetDeletionTimestamp() == nil {
   631  		return nil
   632  	}
   633  	if labels := rt.GetLabels(); labels != nil {
   634  		var request reconcile.Request
   635  		request.Name = labels[oam.LabelAppName]
   636  		request.Namespace = labels[oam.LabelAppNamespace]
   637  		if request.Namespace != "" && request.Name != "" {
   638  			return []reconcile.Request{request}
   639  		}
   640  	}
   641  	return nil
   642  }
   643  
   644  func timeReconcile(app *v1beta1.Application) func() {
   645  	t := time.Now()
   646  	beginPhase := string(app.Status.Phase)
   647  	return func() {
   648  		v := time.Since(t).Seconds()
   649  		metrics.ApplicationReconcileTimeHistogram.WithLabelValues(beginPhase, string(app.Status.Phase)).Observe(v)
   650  	}
   651  }
   652  
   653  func parseOptions(args core.Args) options {
   654  	return options{
   655  		appRevisionLimit:     args.AppRevisionLimit,
   656  		concurrentReconciles: args.ConcurrentReconciles,
   657  		ignoreAppNoCtrlReq:   args.IgnoreAppWithoutControllerRequirement,
   658  		controllerVersion:    version.VelaVersion,
   659  	}
   660  }
   661  
   662  func (r *Reconciler) matchControllerRequirement(app *v1beta1.Application) bool {
   663  	if app.Annotations != nil {
   664  		if requireVersion, ok := app.Annotations[oam.AnnotationControllerRequirement]; ok {
   665  			return requireVersion == r.controllerVersion
   666  		}
   667  	}
   668  
   669  	return !r.ignoreAppNoCtrlReq
   670  }
   671  
   672  const (
   673  	// ComponentNamespaceContextKey is the key in context that defines the override namespace of component
   674  	ComponentNamespaceContextKey contextKey = iota
   675  	// ComponentContextKey is the key in context that records the component
   676  	ComponentContextKey
   677  	// ReplicaKeyContextKey is the key in context that records the replica key
   678  	ReplicaKeyContextKey
   679  	// OriginalAppKey is the key in the context that records the in coming original app
   680  	OriginalAppKey
   681  )
   682  
   683  func withOriginalApp(ctx context.Context, app *v1beta1.Application) context.Context {
   684  	return context.WithValue(ctx, OriginalAppKey, app.DeepCopy())
   685  }
   686  
   687  func originalAppFrom(ctx context.Context) (*v1beta1.Application, bool) {
   688  	app, ok := ctx.Value(OriginalAppKey).(*v1beta1.Application)
   689  	return app, ok
   690  }
   691  
   692  func setVelaVersion(app *v1beta1.Application) {
   693  	if annotations := app.GetAnnotations(); annotations == nil || annotations[oam.AnnotationKubeVelaVersion] == "" {
   694  		metav1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationKubeVelaVersion, version.VelaVersion)
   695  	}
   696  }