From 4903eb7cb2d82da7e17f780db8aaf08b4c710670 Mon Sep 17 00:00:00 2001 From: wyike <77846369+wangyikewxgm@users.noreply.github.com> Date: Fri, 19 Mar 2021 15:04:01 +0800 Subject: [PATCH] refactor ac reconcile so that we can reuse it in new AC (#1236) * refactor ac reconcile so that it returns the status * add appContext to reuse appconfig reconcile logic * fix lint * update makefile for verbose info * fix appcontext register error * fix application configuration finalizer bug Co-authored-by: Ryan Zhang --- .../v1alpha2/applicationcontext_types.go | 48 +++ apis/core.oam.dev/v1alpha2/register.go | 9 + .../v1alpha2/zz_generated.deepcopy.go | 74 +++++ .../core.oam.dev_applicationcontexts.yaml | 305 +++++++++++++++++ .../core.oam.dev_applicationcontexts.yaml | 306 ++++++++++++++++++ .../applicationconfiguration.go | 47 ++- .../applicationcontext_controller.go | 113 +++++++ ...on-workload.go => application_workload.go} | 0 pkg/controller/core.oam.dev/v1alpha2/setup.go | 3 +- 9 files changed, 879 insertions(+), 26 deletions(-) create mode 100644 apis/core.oam.dev/v1alpha2/applicationcontext_types.go create mode 100644 charts/vela-core/crds/core.oam.dev_applicationcontexts.yaml create mode 100644 legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationcontexts.yaml create mode 100644 pkg/controller/core.oam.dev/v1alpha2/applicationcontext/applicationcontext_controller.go rename pkg/controller/core.oam.dev/v1alpha2/applicationrollout/{application-workload.go => application_workload.go} (100%) diff --git a/apis/core.oam.dev/v1alpha2/applicationcontext_types.go b/apis/core.oam.dev/v1alpha2/applicationcontext_types.go new file mode 100644 index 000000000..c004dda7b --- /dev/null +++ b/apis/core.oam.dev/v1alpha2/applicationcontext_types.go @@ -0,0 +1,48 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ApplicationContextSpec is the spec of ApplicationContext +type ApplicationContextSpec struct { + // ApplicationRevisionName points to the snapshot of an Application with all its closure + ApplicationRevisionName string `json:"applicationRevisionName"` +} + +// ApplicationContext is the Schema for the ApplicationContext API +// +kubebuilder:object:root=true +// +kubebuilder:resource:shortName=appcontext,categories={oam} +// +kubebuilder:subresource:status +type ApplicationContext struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ApplicationContextSpec `json:"spec,omitempty"` + // we need to reuse the AC status + Status ApplicationConfigurationStatus `json:"status,omitempty"` +} + +// ApplicationContextList contains a list of ApplicationContext +// +kubebuilder:object:root=true +type ApplicationContextList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ApplicationContext `json:"items"` +} diff --git a/apis/core.oam.dev/v1alpha2/register.go b/apis/core.oam.dev/v1alpha2/register.go index 870ff5a69..5d8cb276c 100644 --- a/apis/core.oam.dev/v1alpha2/register.go +++ b/apis/core.oam.dev/v1alpha2/register.go @@ -85,6 +85,14 @@ var ( ApplicationConfigurationGroupVersionKind = SchemeGroupVersion.WithKind(ApplicationConfigurationKind) ) +// ApplicationContext type metadata. +var ( + ApplicationContextKind = reflect.TypeOf(ApplicationContext{}).Name() + ApplicationContextGroupKind = schema.GroupKind{Group: Group, Kind: ApplicationContextKind}.String() + ApplicationContextKindAPIVersion = ApplicationContextKind + "." + SchemeGroupVersion.String() + ApplicationContextGroupVersionKind = SchemeGroupVersion.WithKind(ApplicationContextKind) +) + // ContainerizedWorkload type metadata. var ( ContainerizedWorkloadKind = reflect.TypeOf(ContainerizedWorkload{}).Name() @@ -146,4 +154,5 @@ func init() { SchemeBuilder.Register(&Application{}, &ApplicationList{}) SchemeBuilder.Register(&AppRollout{}, &AppRolloutList{}) SchemeBuilder.Register(&ApplicationRevision{}, &ApplicationRevisionList{}) + SchemeBuilder.Register(&ApplicationContext{}, &ApplicationContextList{}) } diff --git a/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go b/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go index 8d5e87a0d..2a14c0081 100644 --- a/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go +++ b/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go @@ -418,6 +418,80 @@ func (in *ApplicationConfigurationStatus) DeepCopy() *ApplicationConfigurationSt return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationContext) DeepCopyInto(out *ApplicationContext) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationContext. +func (in *ApplicationContext) DeepCopy() *ApplicationContext { + if in == nil { + return nil + } + out := new(ApplicationContext) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApplicationContext) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationContextList) DeepCopyInto(out *ApplicationContextList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ApplicationContext, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationContextList. +func (in *ApplicationContextList) DeepCopy() *ApplicationContextList { + if in == nil { + return nil + } + out := new(ApplicationContextList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApplicationContextList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationContextSpec) DeepCopyInto(out *ApplicationContextSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationContextSpec. +func (in *ApplicationContextSpec) DeepCopy() *ApplicationContextSpec { + if in == nil { + return nil + } + out := new(ApplicationContextSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApplicationList) DeepCopyInto(out *ApplicationList) { *out = *in diff --git a/charts/vela-core/crds/core.oam.dev_applicationcontexts.yaml b/charts/vela-core/crds/core.oam.dev_applicationcontexts.yaml new file mode 100644 index 000000000..9296def88 --- /dev/null +++ b/charts/vela-core/crds/core.oam.dev_applicationcontexts.yaml @@ -0,0 +1,305 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.4 + creationTimestamp: null + name: applicationcontexts.core.oam.dev +spec: + group: core.oam.dev + names: + categories: + - oam + kind: ApplicationContext + listKind: ApplicationContextList + plural: applicationcontexts + shortNames: + - appcontext + singular: applicationcontext + scope: Namespaced + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: ApplicationContext is the Schema for the ApplicationContext API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ApplicationContextSpec is the spec of ApplicationContext + properties: + applicationRevisionName: + description: ApplicationRevisionName points to the snapshot of an Application with all its closure + type: string + required: + - applicationRevisionName + type: object + status: + description: we need to reuse the AC status + properties: + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: LastTransitionTime is the last time this condition transitioned from one status to another. + format: date-time + type: string + message: + description: A Message containing details about this condition's last transition from one status to another, if any. + type: string + reason: + description: A Reason for this condition's last transition from one status to another. + type: string + status: + description: Status of this condition; is it currently True, False, or Unknown? + type: string + type: + description: Type of this condition. At most one of each condition type may apply to a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + dependency: + description: DependencyStatus represents the observed state of the dependency of an ApplicationConfiguration. + properties: + unsatisfied: + items: + description: UnstaifiedDependency describes unsatisfied dependency flow between one pair of objects. + properties: + from: + description: DependencyFromObject represents the object that dependency data comes from. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + fieldPath: + type: string + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + reason: + type: string + to: + description: DependencyToObject represents the object that dependency data goes to. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + fieldPaths: + items: + type: string + type: array + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + required: + - from + - reason + - to + type: object + type: array + type: object + historyWorkloads: + description: HistoryWorkloads will record history but still working revision workloads. + items: + description: HistoryWorkload contain the old component revision that are still running + properties: + revision: + description: Revision of this workload + type: string + workloadRef: + description: Reference to running workload. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + type: object + type: array + observedGeneration: + description: The generation observed by the appConfig controller. + format: int64 + type: integer + rollingStatus: + description: RollingStatus indicates what phase are we in the rollout phase + type: string + status: + description: Status is a place holder for a customized controller to fill if it needs a single place to summarize the status of the entire application + type: string + workloads: + description: Workloads created by this ApplicationConfiguration. + items: + description: A WorkloadStatus represents the status of a workload. + properties: + appliedComponentRevision: + description: AppliedComponentRevision indicates the applied component revision name of this workload + type: string + componentName: + description: ComponentName that produced this workload. + type: string + componentRevisionName: + description: ComponentRevisionName of current component + type: string + dependencyUnsatisfied: + description: DependencyUnsatisfied notify does the workload has dependency unsatisfied + type: boolean + scopes: + description: Scopes associated with this workload. + items: + description: A WorkloadScope represents a scope associated with a workload and its status + properties: + scopeRef: + description: Reference to a scope created by an ApplicationConfiguration. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + status: + description: Status is a place holder for a customized controller to fill if it needs a single place to summarize the status of the scope + type: string + required: + - scopeRef + type: object + type: array + status: + description: Status is a place holder for a customized controller to fill if it needs a single place to summarize the entire status of the workload + type: string + traits: + description: Traits associated with this workload. + items: + description: A WorkloadTrait represents a trait associated with a workload and its status + properties: + appliedGeneration: + description: AppliedGeneration indicates the generation observed by the appConfig controller. The same field is also recorded in the annotations of traits. A trait is possible to be deleted from cluster after created. This field is useful to track the observed generation of traits after they are deleted. + format: int64 + type: integer + dependencyUnsatisfied: + description: DependencyUnsatisfied notify does the trait has dependency unsatisfied + type: boolean + message: + description: Message will allow controller to leave some additional information for this trait + type: string + status: + description: Status is a place holder for a customized controller to fill if it needs a single place to summarize the status of the trait + type: string + traitRef: + description: Reference to a trait created by an ApplicationConfiguration. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + required: + - traitRef + type: object + type: array + workloadRef: + description: Reference to a workload created by an ApplicationConfiguration. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationcontexts.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationcontexts.yaml new file mode 100644 index 000000000..4f40295d8 --- /dev/null +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationcontexts.yaml @@ -0,0 +1,306 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.4 + creationTimestamp: null + name: applicationcontexts.core.oam.dev +spec: + group: core.oam.dev + names: + categories: + - oam + kind: ApplicationContext + listKind: ApplicationContextList + plural: applicationcontexts + shortNames: + - appcontext + singular: applicationcontext + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: ApplicationContext is the Schema for the ApplicationContext API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ApplicationContextSpec is the spec of ApplicationContext + properties: + applicationRevisionName: + description: ApplicationRevisionName points to the snapshot of an Application with all its closure + type: string + required: + - applicationRevisionName + type: object + status: + description: we need to reuse the AC status + properties: + conditions: + description: Conditions of the resource. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: LastTransitionTime is the last time this condition transitioned from one status to another. + format: date-time + type: string + message: + description: A Message containing details about this condition's last transition from one status to another, if any. + type: string + reason: + description: A Reason for this condition's last transition from one status to another. + type: string + status: + description: Status of this condition; is it currently True, False, or Unknown? + type: string + type: + description: Type of this condition. At most one of each condition type may apply to a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + dependency: + description: DependencyStatus represents the observed state of the dependency of an ApplicationConfiguration. + properties: + unsatisfied: + items: + description: UnstaifiedDependency describes unsatisfied dependency flow between one pair of objects. + properties: + from: + description: DependencyFromObject represents the object that dependency data comes from. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + fieldPath: + type: string + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + reason: + type: string + to: + description: DependencyToObject represents the object that dependency data goes to. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + fieldPaths: + items: + type: string + type: array + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + required: + - from + - reason + - to + type: object + type: array + type: object + historyWorkloads: + description: HistoryWorkloads will record history but still working revision workloads. + items: + description: HistoryWorkload contain the old component revision that are still running + properties: + revision: + description: Revision of this workload + type: string + workloadRef: + description: Reference to running workload. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + type: object + type: array + observedGeneration: + description: The generation observed by the appConfig controller. + format: int64 + type: integer + rollingStatus: + description: RollingStatus indicates what phase are we in the rollout phase + type: string + status: + description: Status is a place holder for a customized controller to fill if it needs a single place to summarize the status of the entire application + type: string + workloads: + description: Workloads created by this ApplicationConfiguration. + items: + description: A WorkloadStatus represents the status of a workload. + properties: + appliedComponentRevision: + description: AppliedComponentRevision indicates the applied component revision name of this workload + type: string + componentName: + description: ComponentName that produced this workload. + type: string + componentRevisionName: + description: ComponentRevisionName of current component + type: string + dependencyUnsatisfied: + description: DependencyUnsatisfied notify does the workload has dependency unsatisfied + type: boolean + scopes: + description: Scopes associated with this workload. + items: + description: A WorkloadScope represents a scope associated with a workload and its status + properties: + scopeRef: + description: Reference to a scope created by an ApplicationConfiguration. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + status: + description: Status is a place holder for a customized controller to fill if it needs a single place to summarize the status of the scope + type: string + required: + - scopeRef + type: object + type: array + status: + description: Status is a place holder for a customized controller to fill if it needs a single place to summarize the entire status of the workload + type: string + traits: + description: Traits associated with this workload. + items: + description: A WorkloadTrait represents a trait associated with a workload and its status + properties: + appliedGeneration: + description: AppliedGeneration indicates the generation observed by the appConfig controller. The same field is also recorded in the annotations of traits. A trait is possible to be deleted from cluster after created. This field is useful to track the observed generation of traits after they are deleted. + format: int64 + type: integer + dependencyUnsatisfied: + description: DependencyUnsatisfied notify does the trait has dependency unsatisfied + type: boolean + message: + description: Message will allow controller to leave some additional information for this trait + type: string + status: + description: Status is a place holder for a customized controller to fill if it needs a single place to summarize the status of the trait + type: string + traitRef: + description: Reference to a trait created by an ApplicationConfiguration. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + required: + - traitRef + type: object + type: array + workloadRef: + description: Reference to a workload created by an ApplicationConfiguration. + properties: + apiVersion: + description: APIVersion of the referenced object. + type: string + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + uid: + description: UID of the referenced object. + type: string + required: + - apiVersion + - kind + - name + type: object + type: object + type: array + type: object + type: object + version: v1alpha2 + versions: + - name: v1alpha2 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go index e154b026b..2b6d4454f 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go @@ -230,13 +230,6 @@ func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (reconcile.R return reconcile.Result{}, errors.Wrap(err, errGetAppConfig) } - return r.ACReconcile(ctx, ac, log) -} - -// ACReconcile contains all the reconcile logic of an AC, it can be used by other controller -func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, - log logging.Logger) (result reconcile.Result, returnErr error) { - acPatch := ac.DeepCopy() ctx = util.SetNamespaceInCtx(ctx, ac.Namespace) if ac.ObjectMeta.DeletionTimestamp.IsZero() { if registerFinalizers(ac) { @@ -246,7 +239,7 @@ func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2 } else { if err := r.workloads.Finalize(ctx, ac); err != nil { log.Debug("Failed to finalize workloads", "workloads status", ac.Status.Workloads, - "error", err, "requeue-after", result.RequeueAfter) + "error", err) r.record.Event(ac, event.Warning(reasonCannotFinalizeWorkloads, err)) ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errFinalizeWorkloads))) return reconcile.Result{}, errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus) @@ -254,16 +247,21 @@ func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2 return reconcile.Result{}, errors.Wrap(r.client.Update(ctx, ac), errUpdateAppConfigStatus) } - // make sure this is the last functional defer function to be called - defer func() { - // always update ac status and set the error - returnErr = errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus) - // Make sure if error occurs, reconcile will not happen too frequency - if returnErr != nil { - result.RequeueAfter = 0 - } - }() + reconResult := r.ACReconcile(ctx, ac, log) + // always update ac status and set the error + err := errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus) + // use the controller build-in backoff mechanism if an error occurs + if err != nil { + reconResult.RequeueAfter = 0 + } + return reconResult, err +} +// ACReconcile contains all the reconcile logic of an AC, it can be used by other controller +func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2.ApplicationConfiguration, + log logging.Logger) (result reconcile.Result) { + + acPatch := ac.DeepCopy() // execute the posthooks at the end no matter what defer func() { updateObservedGeneration(ac) @@ -274,7 +272,6 @@ func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2 r.record.Event(ac, event.Warning(reasonCannotExecutePosthooks, err)) ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errExecutePosthooks))) result = exeResult - returnErr = errors.Wrap(r.UpdateStatus(ctx, ac), errUpdateAppConfigStatus) return } r.record.Event(ac, event.Normal(reasonExecutePosthook, "Successfully executed a posthook", "posthook name", name)) @@ -288,7 +285,7 @@ func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2 log.Debug("Failed to execute pre-hooks", "hook name", name, "error", err, "requeue-after", result.RequeueAfter) r.record.Event(ac, event.Warning(reasonCannotExecutePrehooks, err)) ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errExecutePrehooks))) - return result, nil + return result } r.record.Event(ac, event.Normal(reasonExecutePrehook, "Successfully executed a prehook", "prehook name ", name)) } @@ -304,7 +301,7 @@ func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2 ac.SetConditions(v1alpha1.Unavailable()) ac.Status.RollingStatus = v1alpha2.InactiveAfterRollingCompleted // TODO: GC the traits/workloads - return reconcile.Result{}, nil + return reconcile.Result{} } } @@ -313,7 +310,7 @@ func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2 log.Info("Cannot render components", "error", err) r.record.Event(ac, event.Warning(reasonCannotRenderComponents, err)) ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errRenderComponents))) - return reconcile.Result{}, nil + return reconcile.Result{} } log.Debug("Successfully rendered components", "workloads", len(workloads)) r.record.Event(ac, event.Normal(reasonRenderComponents, "Successfully rendered components", "workloads", strconv.Itoa(len(workloads)))) @@ -323,7 +320,7 @@ func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2 log.Debug("Cannot apply workload", "error", err) r.record.Event(ac, event.Warning(reasonCannotApplyComponents, err)) ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errApplyComponents))) - return reconcile.Result{}, nil + return reconcile.Result{} } log.Debug("Successfully applied components", "workloads", len(workloads)) r.record.Event(ac, event.Normal(reasonApplyComponents, "Successfully applied components", "workloads", strconv.Itoa(len(workloads)))) @@ -344,13 +341,13 @@ func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2 log.Debug("confirm component can't be garbage collected", "error", err) record.Event(ac, event.Warning(reasonCannotGGComponents, err)) ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errGCComponent))) - return reconcile.Result{}, nil + return reconcile.Result{} } if err := r.client.Delete(ctx, &e); resource.IgnoreNotFound(err) != nil { log.Debug("Cannot garbage collect component", "error", err) record.Event(ac, event.Warning(reasonCannotGGComponents, err)) ac.SetConditions(v1alpha1.ReconcileError(errors.Wrap(err, errGCComponent))) - return reconcile.Result{}, nil + return reconcile.Result{} } log.Debug("Garbage collected resource") record.Event(ac, event.Normal(reasonGGComponent, "Successfully garbage collected component")) @@ -367,7 +364,7 @@ func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2 } // the defer function will do the final status update - return reconcile.Result{RequeueAfter: waitTime}, nil + return reconcile.Result{RequeueAfter: waitTime} } // confirmDeleteOnApplyOnceMode will confirm whether the workload can be delete or not in apply once only enabled mode diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationcontext/applicationcontext_controller.go b/pkg/controller/core.oam.dev/v1alpha2/applicationcontext/applicationcontext_controller.go new file mode 100644 index 000000000..6668a2548 --- /dev/null +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationcontext/applicationcontext_controller.go @@ -0,0 +1,113 @@ +package applicationcontext + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/crossplane/crossplane-runtime/pkg/event" + "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" + core "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev" + ac "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration" + "github.com/oam-dev/kubevela/pkg/oam/discoverymapper" + "github.com/oam-dev/kubevela/pkg/oam/util" +) + +// Reconcile error strings. +const ( + errGetAppContex = "cannot get application context" + errGetAppRevision = "cannot get the application revision the context refers to" + errUpdateAppContextStatus = "cannot update application context status" +) + +const reconcileTimeout = 1 * time.Minute + +// Reconciler reconciles an Application Context by constructing an in-memory +// application configuration and reuse its reconcile logic +type Reconciler struct { + client client.Client + log logging.Logger + record event.Recorder + mgr ctrl.Manager + applyMode core.ApplyOnceOnlyMode +} + +// Reconcile reconcile an application context +func (r *Reconciler) Reconcile(request reconcile.Request) (reconcile.Result, error) { + r.log.Debug("Reconciling") + ctx, cancel := context.WithTimeout(context.Background(), reconcileTimeout) + defer cancel() + // fetch the app context + appContext := &v1alpha2.ApplicationContext{} + if err := r.client.Get(ctx, request.NamespacedName, appContext); err != nil { + if apierrors.IsNotFound(err) { + // stop processing this resource + return ctrl.Result{}, nil + } + return reconcile.Result{}, errors.Wrap(err, errGetAppContex) + } + + ctx = util.SetNamespaceInCtx(ctx, appContext.Namespace) + dm, err := discoverymapper.New(r.mgr.GetConfig()) + if err != nil { + return reconcile.Result{}, fmt.Errorf("create discovery dm fail %w", err) + } + // fetch the appRevision it points to + appRevision := &v1alpha2.ApplicationRevision{} + key := types.NamespacedName{Namespace: appContext.Namespace, Name: appContext.Spec.ApplicationRevisionName} + if err := r.client.Get(ctx, key, appRevision); err != nil { + if apierrors.IsNotFound(err) { + // stop processing this resource + return ctrl.Result{}, nil + } + return reconcile.Result{}, errors.Wrap(err, errGetAppRevision) + } + + // copy the status + appConfig := appRevision.Spec.ApplicationConfiguration.DeepCopy() + appConfig.Status = appContext.Status + // call into the acReconciler and copy the status back + acReconciler := ac.NewReconciler(r.mgr, dm, r.log, ac.WithRecorder(r.record), ac.WithApplyOnceOnlyMode(r.applyMode)) + reconResult := acReconciler.ACReconcile(ctx, appConfig, r.log) + appContext.Status = appConfig.Status + // always update ac status and set the error + err = errors.Wrap(r.client.Status().Update(ctx, appContext), errUpdateAppContextStatus) + // use the controller build-in backoff mechanism if an error occurs + if err != nil { + reconResult.RequeueAfter = 0 + } + return reconResult, err +} + +// SetupWithManager setup the controller with manager +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + r.record = event.NewAPIRecorder(mgr.GetEventRecorderFor("AppRollout")). + WithAnnotations("controller", "AppRollout") + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha2.ApplicationContext{}). + Owns(&v1alpha2.Application{}). + Complete(r) +} + +// Setup adds a controller that reconciles ApplicationContext +func Setup(mgr ctrl.Manager, args core.Args, l logging.Logger) error { + name := "oam/" + strings.ToLower(v1alpha2.ApplicationContextGroupKind) + record := event.NewAPIRecorder(mgr.GetEventRecorderFor(name)) + reconciler := Reconciler{ + client: mgr.GetClient(), + mgr: mgr, + log: l.WithValues("controller", name), + record: record, + applyMode: args.ApplyMode, + } + return reconciler.SetupWithManager(mgr) +} diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationrollout/application-workload.go b/pkg/controller/core.oam.dev/v1alpha2/applicationrollout/application_workload.go similarity index 100% rename from pkg/controller/core.oam.dev/v1alpha2/applicationrollout/application-workload.go rename to pkg/controller/core.oam.dev/v1alpha2/applicationrollout/application_workload.go diff --git a/pkg/controller/core.oam.dev/v1alpha2/setup.go b/pkg/controller/core.oam.dev/v1alpha2/setup.go index c6d5bfe39..2cdcb635c 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/setup.go +++ b/pkg/controller/core.oam.dev/v1alpha2/setup.go @@ -23,6 +23,7 @@ import ( controller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev" "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application" "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration" + "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/applicationcontext" "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/applicationrollout" "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/core/components/componentdefinition" "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope" @@ -36,7 +37,7 @@ func Setup(mgr ctrl.Manager, args controller.Args, l logging.Logger) error { for _, setup := range []func(ctrl.Manager, controller.Args, logging.Logger) error{ applicationconfiguration.Setup, containerizedworkload.Setup, manualscalertrait.Setup, healthscope.Setup, - application.Setup, applicationrollout.Setup, + application.Setup, applicationrollout.Setup, applicationcontext.Setup, traitdefinition.Setup, componentdefinition.Setup, } { if err := setup(mgr, args, l); err != nil {