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 }