mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-27 16:17:34 +00:00
remove appContext from app/appRollout controller (#1774)
* refine assemble and dispatch Signed-off-by: roy wang <seiwy2010@gmail.com> * remove app context in app controller modify clean up app revision remove old resource tracker related logic fix unit tests Signed-off-by: roy wang <seiwy2010@gmail.com> * fix e2e-test - get rid of appCtx in test cases - fix test cases according other logic changes in app controller remove whole appcontext_test.go file disable rollout related e2e test provisionally disable resource tracker related e2e test provisionally Signed-off-by: roy wang <seiwy2010@gmail.com> * add finalizer logic for app controller Signed-off-by: roywang <seiwy2010@gmail.com> * add new apply option MustBeControllableByAny make dispatch idempotent Signed-off-by: roywang <seiwy2010@gmail.com> * refactor rollout * fix rollout finalize succeed Signed-off-by: roywang <seiwy2010@gmail.com> * add update trait and gc test fix lint * fix flaky e2e test Signed-off-by: roywang <seiwy2010@gmail.com> * fix comment * fix comments and add sourceRevision dispatch delete useless Signed-off-by: Yue Wang <seiwy2010@gmail.com> * fix app finalizer backward compatible Signed-off-by: roywang <seiwy2010@gmail.com> * fix backward compatability for deprecation of appContext add unit test for apply option add e2e test Signed-off-by: Yue Wang <seiwy2010@gmail.com> * fix app controller unit test Signed-off-by: Yue Wang <seiwy2010@gmail.com> * refine app controller apply logic Signed-off-by: Yue Wang <seiwy2010@gmail.com> * fix e2e test of resource tracker fix e2e test of rollout plan fix flaky e2e tests Signed-off-by: Yue Wang <seiwy2010@gmail.com> * refine comments and remove useless codes Signed-off-by: Yue Wang <seiwy2010@gmail.com> * disable appCtx controller add Component handler into app controller Signed-off-by: Yue Wang <seiwy2010@gmail.com> Co-authored-by: wangyike <wangyike.wyk@alibaba-inc.com>
This commit is contained in:
+63
-121
@@ -18,13 +18,13 @@ package applicationrollout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
"github.com/pkg/errors"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/klog/v2"
|
||||
@@ -33,13 +33,10 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
oamv1alpha2 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/common/rollout"
|
||||
oamctrl "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
@@ -110,119 +107,86 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (res reconcile.Result, retErr e
|
||||
}
|
||||
|
||||
// DoReconcile is real reconcile logic for appRollout.
|
||||
// 1.prepare rollout info: use assemble module in application pkg to generate manifest with appRevision
|
||||
// 2.determine which component is the common component between source and target AppRevision
|
||||
// 3.if target workload isn't exist yet, template the targetAppRevision to apply target manifest
|
||||
// 4.extract target workload and source workload(if sourceAppRevision not empty)
|
||||
// 5.generate a rolloutPlan controller with source and target workload and call rolloutPlan's reconcile func
|
||||
// 6.handle output status
|
||||
// !!! Note the AppRollout object should not be updated in this function as it could be logically used in Application reconcile loop which does not have real AppRollout object.
|
||||
func (r *Reconciler) DoReconcile(ctx context.Context, appRollout *v1beta1.AppRollout) (res reconcile.Result, retErr error) {
|
||||
func (r *Reconciler) DoReconcile(ctx context.Context, appRollout *v1beta1.AppRollout) (reconcile.Result, error) {
|
||||
if len(appRollout.Status.RollingState) == 0 {
|
||||
appRollout.Status.ResetStatus()
|
||||
}
|
||||
targetAppRevisionName := appRollout.Spec.TargetAppRevisionName
|
||||
sourceAppRevisionName := appRollout.Spec.SourceAppRevisionName
|
||||
var err error
|
||||
|
||||
// no need to proceed if rollout is already in a terminal state and there is no source/target change
|
||||
doneReconcile := r.handleRollingTerminated(*appRollout, targetAppRevisionName, sourceAppRevisionName)
|
||||
doneReconcile := r.handleRollingTerminated(*appRollout)
|
||||
if doneReconcile {
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
h := rolloutHandler{Reconciler: r, appRollout: appRollout}
|
||||
// handle rollout target/source change (only if it's not deleting already)
|
||||
if isRolloutModified(*appRollout) {
|
||||
klog.InfoS("rollout target changed, restart the rollout", "new source", sourceAppRevisionName,
|
||||
"new target", targetAppRevisionName)
|
||||
r.record.Event(appRollout, event.Normal("Rollout Restarted",
|
||||
"rollout target changed, restart the rollout", "new source", sourceAppRevisionName,
|
||||
"new target", targetAppRevisionName))
|
||||
// we are okay to move directly to restart the rollout since we are at the terminal state
|
||||
// however, we need to make sure we properly finalizing the existing rollout before restart if it's
|
||||
// still in the middle of rolling out
|
||||
if appRollout.Status.RollingState != v1alpha1.RolloutSucceedState &&
|
||||
appRollout.Status.RollingState != v1alpha1.RolloutFailedState {
|
||||
// continue to handle the previous resources until we are okay to move forward
|
||||
targetAppRevisionName = appRollout.Status.LastUpgradedTargetAppRevision
|
||||
sourceAppRevisionName = appRollout.Status.LastSourceAppRevision
|
||||
} else {
|
||||
// mark so that we don't think we are modified again
|
||||
appRollout.Status.LastUpgradedTargetAppRevision = targetAppRevisionName
|
||||
appRollout.Status.LastSourceAppRevision = sourceAppRevisionName
|
||||
}
|
||||
appRollout.Status.StateTransition(v1alpha1.RollingModifiedEvent)
|
||||
h.handleRolloutModified()
|
||||
} else {
|
||||
// except modified in middle of one rollout, in most cases use real source/target in appRollout and revision as this round reconcile
|
||||
h.sourceRevName = appRollout.Spec.SourceAppRevisionName
|
||||
h.targetRevName = appRollout.Spec.TargetAppRevisionName
|
||||
}
|
||||
|
||||
// Get the source application first
|
||||
var sourceApRev, targetAppRev *oamv1alpha2.ApplicationRevision
|
||||
var sourceApp, targetApp *oamv1alpha2.ApplicationContext
|
||||
var err error
|
||||
// call assemble func generate source and target manifest
|
||||
if err = h.prepareRollout(ctx); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
if appRollout.Status.RollingState == v1alpha1.RolloutDeletingState {
|
||||
if sourceAppRevisionName == "" {
|
||||
klog.InfoS("source app fields not filled, this is a scale operation", "appRollout", klog.KRef(appRollout.Namespace, appRollout.Name))
|
||||
} else {
|
||||
sourceApRev, sourceApp, err = r.getSourceAppContexts(ctx,
|
||||
appRollout.Spec.ComponentList, appRollout.Status.RollingState, sourceAppRevisionName)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
// Get the
|
||||
targetAppRev, targetApp, err = r.getTargetApps(ctx, appRollout.Spec.ComponentList,
|
||||
appRollout.Status.RollingState, targetAppRevisionName)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if sourceApp == nil && targetApp == nil {
|
||||
// we only support one workload rollout now, so here is determine witch component is need to rollout
|
||||
if err = h.determineRolloutComponent(); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
var sourceWorkload, targetWorkload *unstructured.Unstructured
|
||||
|
||||
// we should handle two special cases before call rolloutPlan Reconcile
|
||||
switch h.appRollout.Status.RollingState {
|
||||
case v1alpha1.RolloutDeletingState:
|
||||
// application has been deleted, the related appRev haven't removed
|
||||
if h.sourceAppRevision == nil && h.targetAppRevision == nil {
|
||||
klog.InfoS("Both the target and the source app are gone", "appRollout",
|
||||
klog.KRef(appRollout.Namespace, appRollout.Name), "rolling state", appRollout.Status.RollingState)
|
||||
appRollout.Status.StateTransition(v1alpha1.RollingFinalizedEvent)
|
||||
h.appRollout.Status.StateTransition(v1alpha1.RollingFinalizedEvent)
|
||||
// update the appRollout status
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
} else {
|
||||
// TODO: try to refactor this into a method with reasonable number of parameters and output
|
||||
if sourceAppRevisionName == "" {
|
||||
klog.Info("source app fields not filled, this is a scale operation")
|
||||
} else {
|
||||
sourceApRev, sourceApp, err = r.getSourceAppContexts(ctx,
|
||||
appRollout.Spec.ComponentList, appRollout.Status.RollingState, sourceAppRevisionName)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
// check if the app is templated
|
||||
if sourceApp.Status.RollingStatus != types.RollingTemplated {
|
||||
klog.Info("source app revision is not ready for rolling yet", "application revision", sourceAppRevisionName)
|
||||
r.record.Event(appRollout, event.Normal("Rollout Paused",
|
||||
"source app revision is not ready for rolling yet", "application revision", sourceApp.GetName()))
|
||||
return ctrl.Result{RequeueAfter: 3 * time.Second}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Get the target application revision after the source app is templated
|
||||
targetAppRev, targetApp, err = r.getTargetApps(ctx, appRollout.Spec.ComponentList,
|
||||
appRollout.Status.RollingState, targetAppRevisionName)
|
||||
case v1alpha1.LocatingTargetAppState:
|
||||
// dispatch sourceWorkload
|
||||
err = h.templateSourceManifest(ctx)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
// this ensures that we handle the target app init only once
|
||||
appRollout.Status.StateTransition(v1alpha1.AppLocatedEvent)
|
||||
|
||||
// check if the app is templated
|
||||
if targetApp.Status.RollingStatus != types.RollingTemplated {
|
||||
r.record.Event(appRollout, event.Normal("Rollout Paused",
|
||||
"target app revision is not ready for rolling yet", "application revision", targetApp.GetName()))
|
||||
return ctrl.Result{RequeueAfter: 3 * time.Second}, nil
|
||||
// target manifest haven't template yet, call dispatch template target manifest firstly
|
||||
err = h.templateTargetManifest(ctx)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
// this ensures that we template workload only once
|
||||
h.appRollout.Status.StateTransition(v1alpha1.AppLocatedEvent)
|
||||
return reconcile.Result{RequeueAfter: 3 * time.Second}, nil
|
||||
default:
|
||||
// in other cases there is no need do anything
|
||||
}
|
||||
|
||||
// we get the real workloads from the spec of the revisions
|
||||
targetWorkload, sourceWorkload, err := r.extractWorkloads(ctx, appRollout.Spec.ComponentList, targetAppRev, sourceApRev)
|
||||
sourceWorkload, targetWorkload, err = h.fetchSourceAndTargetWorkload(ctx)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "cannot fetch the workloads to upgrade", "target application",
|
||||
klog.KRef(appRollout.Namespace, targetAppRevisionName), "source application", klog.KRef(appRollout.Namespace, sourceAppRevisionName),
|
||||
"commonComponent", appRollout.Spec.ComponentList)
|
||||
return ctrl.Result{RequeueAfter: 5 * time.Second}, err
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
klog.InfoS("get the target workload we need to work on", "targetWorkload", klog.KObj(targetWorkload))
|
||||
if sourceWorkload != nil {
|
||||
klog.InfoS("get the source workload we need to work on", "sourceWorkload", klog.KObj(sourceWorkload))
|
||||
}
|
||||
|
||||
// reconcile the rollout part of the spec given the target and source workload
|
||||
rolloutPlanController := rollout.NewRolloutPlanController(r, appRollout, r.record,
|
||||
&appRollout.Spec.RolloutPlan, &appRollout.Status.RolloutStatus, targetWorkload, sourceWorkload)
|
||||
@@ -234,18 +198,19 @@ func (r *Reconciler) DoReconcile(ctx context.Context, appRollout *v1beta1.AppRol
|
||||
appRollout.Status.LastUpgradedTargetAppRevision = appRollout.Spec.TargetAppRevisionName
|
||||
appRollout.Status.LastSourceAppRevision = appRollout.Spec.SourceAppRevisionName
|
||||
}
|
||||
|
||||
if rolloutStatus.RollingState == v1alpha1.RolloutSucceedState {
|
||||
klog.InfoS("rollout succeeded, record the source and target app revision", "source", sourceAppRevisionName,
|
||||
"target", targetAppRevisionName)
|
||||
if err = r.finalizeRollingSucceeded(ctx, sourceApp, targetApp); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
err = h.finalizeRollingSucceeded(ctx)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
klog.InfoS("rollout succeeded, record the source and target app revision", "source", appRollout.Spec.SourceAppRevisionName,
|
||||
"target", appRollout.Spec.TargetAppRevisionName)
|
||||
} else if rolloutStatus.RollingState == v1alpha1.RolloutFailedState {
|
||||
klog.InfoS("rollout failed, record the source and target app revision", "source", sourceAppRevisionName,
|
||||
"target", targetAppRevisionName, "revert on deletion", appRollout.Spec.RevertOnDelete)
|
||||
klog.InfoS("rollout failed, record the source and target app revision", "source", appRollout.Spec.SourceAppRevisionName,
|
||||
"target", appRollout.Spec.TargetAppRevisionName, "revert on deletion", appRollout.Spec.RevertOnDelete)
|
||||
|
||||
}
|
||||
// update the appRollout status
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -289,43 +254,20 @@ func (r *Reconciler) handleFinalizer(ctx context.Context, appRollout *v1beta1.Ap
|
||||
return false, reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) handleRollingTerminated(appRollout v1beta1.AppRollout, targetAppRevisionName string,
|
||||
sourceAppRevisionName string) bool {
|
||||
func (r *Reconciler) handleRollingTerminated(appRollout v1beta1.AppRollout) bool {
|
||||
// handle rollout completed
|
||||
if appRollout.Status.RollingState == v1alpha1.RolloutSucceedState ||
|
||||
appRollout.Status.RollingState == v1alpha1.RolloutFailedState {
|
||||
if appRollout.Status.LastUpgradedTargetAppRevision == targetAppRevisionName &&
|
||||
appRollout.Status.LastSourceAppRevision == sourceAppRevisionName {
|
||||
klog.InfoS("rollout completed, no need to reconcile", "source", sourceAppRevisionName,
|
||||
"target", targetAppRevisionName)
|
||||
if appRollout.Status.LastUpgradedTargetAppRevision == appRollout.Spec.TargetAppRevisionName &&
|
||||
appRollout.Status.LastSourceAppRevision == appRollout.Spec.TargetAppRevisionName {
|
||||
klog.InfoS("rollout completed, no need to reconcile", "source", appRollout.Spec.SourceAppRevisionName,
|
||||
"target", appRollout.Spec.TargetAppRevisionName)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *Reconciler) finalizeRollingSucceeded(ctx context.Context, sourceApp *oamv1alpha2.ApplicationContext,
|
||||
targetApp *oamv1alpha2.ApplicationContext) error {
|
||||
if sourceApp != nil {
|
||||
// mark the source app as an application revision only so that it stop being reconciled
|
||||
oamutil.RemoveAnnotations(sourceApp, []string{oam.AnnotationAppRollout})
|
||||
oamutil.AddAnnotations(sourceApp, map[string]string{oam.AnnotationAppRevision: strconv.FormatBool(true)})
|
||||
if err := r.Update(ctx, sourceApp); err != nil {
|
||||
klog.ErrorS(err, "cannot add the app revision annotation", "source application",
|
||||
klog.KRef(sourceApp.Namespace, sourceApp.GetName()))
|
||||
return err
|
||||
}
|
||||
}
|
||||
// remove the rollout annotation so that the target appConfig controller can take over the rest of the work
|
||||
oamutil.RemoveAnnotations(targetApp, []string{oam.AnnotationAppRollout, oam.AnnotationRollingComponent})
|
||||
if err := r.Update(ctx, targetApp); err != nil {
|
||||
klog.ErrorS(err, "cannot remove the rollout annotation", "target application",
|
||||
klog.KRef(targetApp.Namespace, targetApp.GetName()))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateStatus updates v1alpha2.AppRollout's Status with retry.RetryOnConflict
|
||||
func (r *Reconciler) updateStatus(ctx context.Context, appRollout *v1beta1.AppRollout) error {
|
||||
status := appRollout.DeepCopy().Status
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
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 applicationrollout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
ktypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/common"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
appUtil "github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1alpha2/applicationrollout"
|
||||
)
|
||||
|
||||
// getTargetApps try to locate the target appRevision and appContext that is responsible for the target
|
||||
// we will create a new appContext when it's not found
|
||||
func (r *Reconciler) getTargetApps(ctx context.Context, componentList []string, rollingState v1alpha1.RollingState,
|
||||
targetAppRevisionName string) (*v1alpha2.ApplicationRevision, *v1alpha2.ApplicationContext, error) {
|
||||
var appRevision v1alpha2.ApplicationRevision
|
||||
var appContext v1alpha2.ApplicationContext
|
||||
namespaceName := oamutil.GetDefinitionNamespaceWithCtx(ctx)
|
||||
if err := r.Get(ctx, ktypes.NamespacedName{Namespace: namespaceName, Name: targetAppRevisionName},
|
||||
&appRevision); err != nil {
|
||||
klog.ErrorS(err, "cannot locate target application revision", "target application revision",
|
||||
klog.KRef(namespaceName, targetAppRevisionName))
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := r.Get(ctx, ktypes.NamespacedName{Namespace: namespaceName, Name: targetAppRevisionName},
|
||||
&appContext); err != nil {
|
||||
if apierrors.IsNotFound(err) && rollingState == v1alpha1.LocatingTargetAppState {
|
||||
klog.InfoS("target application context does not exist yet, create one", "target application revision",
|
||||
klog.KRef(namespaceName, targetAppRevisionName))
|
||||
appContext, err = r.createAppContext(ctx, componentList, &appRevision)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &appRevision, &appContext, nil
|
||||
}
|
||||
// the appContext has to exist by now
|
||||
klog.ErrorS(err, "cannot locate target application context", "target application name",
|
||||
klog.KRef(namespaceName, targetAppRevisionName), "rollingState", rollingState)
|
||||
return nil, nil, err
|
||||
}
|
||||
// special handle the first time we locate the appContext
|
||||
if rollingState == v1alpha1.LocatingTargetAppState {
|
||||
if appContext.Status.RollingStatus == types.RollingTemplated {
|
||||
// force template the target app
|
||||
klog.InfoS("force templating an already templated target application",
|
||||
"target application revision", klog.KRef(namespaceName, targetAppRevisionName))
|
||||
appContext.Status.RollingStatus = types.RollingTemplating
|
||||
if err := r.Status().Update(ctx, &appContext); err != nil {
|
||||
klog.ErrorS(err, "failed to force update target application context to be templating",
|
||||
"target application name", klog.KRef(namespaceName, targetAppRevisionName))
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
err := r.prepareAppContext(ctx, componentList, &appContext)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return &appRevision, &appContext, nil
|
||||
}
|
||||
|
||||
// getTargetApps try to locate the source appRevision and appContext that is responsible for the source
|
||||
func (r *Reconciler) getSourceAppContexts(ctx context.Context, componentList []string, rollingState v1alpha1.RollingState,
|
||||
sourceAppRevisionName string) (*v1alpha2.ApplicationRevision, *v1alpha2.ApplicationContext, error) {
|
||||
var appRevision v1alpha2.ApplicationRevision
|
||||
var appContext v1alpha2.ApplicationContext
|
||||
namespaceName := oamutil.GetDefinitionNamespaceWithCtx(ctx)
|
||||
if err := r.Get(ctx, ktypes.NamespacedName{Namespace: namespaceName, Name: sourceAppRevisionName},
|
||||
&appRevision); err != nil {
|
||||
klog.ErrorS(err, "cannot locate source application revision", "source application revision",
|
||||
klog.KRef(namespaceName, sourceAppRevisionName))
|
||||
return nil, nil, err
|
||||
}
|
||||
// the source app has to exist or there is nothing for us to upgrade from
|
||||
if err := r.Get(ctx, ktypes.NamespacedName{Namespace: namespaceName, Name: sourceAppRevisionName},
|
||||
&appContext); err != nil {
|
||||
// TODO: use the app name as the source Context to upgrade from none-rolling application to rolling
|
||||
klog.ErrorS(err, "cannot locate source application revision", "source application name",
|
||||
klog.KRef(namespaceName, sourceAppRevisionName))
|
||||
return nil, nil, err
|
||||
}
|
||||
// set the AC as rolling if we are still at locating state
|
||||
if rollingState == v1alpha1.LocatingTargetAppState {
|
||||
err := r.prepareAppContext(ctx, componentList, &appContext)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return &appRevision, &appContext, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) prepareAppContext(ctx context.Context, componentList []string,
|
||||
appContext *v1alpha2.ApplicationContext) error {
|
||||
oamutil.RemoveAnnotations(appContext, []string{oam.AnnotationAppRevision})
|
||||
// pass the rolling component to the app
|
||||
oamutil.AddAnnotations(appContext, map[string]string{oam.AnnotationAppRollout: strconv.FormatBool(true)})
|
||||
if len(componentList) != 0 {
|
||||
oamutil.AddAnnotations(appContext, map[string]string{
|
||||
oam.AnnotationRollingComponent: strings.Join(componentList, common.RollingComponentsSep)})
|
||||
}
|
||||
return r.Update(ctx, appContext)
|
||||
}
|
||||
|
||||
func (r *Reconciler) createAppContext(ctx context.Context, componentList []string,
|
||||
appRevision *v1alpha2.ApplicationRevision) (v1alpha2.ApplicationContext, error) {
|
||||
namespaceName := oamutil.GetDefinitionNamespaceWithCtx(ctx)
|
||||
appContext := v1alpha2.ApplicationContext{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: appRevision.GetName(),
|
||||
Namespace: namespaceName,
|
||||
Labels: appRevision.GetLabels(),
|
||||
Annotations: appRevision.GetAnnotations(),
|
||||
OwnerReferences: appRevision.GetOwnerReferences(),
|
||||
},
|
||||
Spec: v1alpha2.ApplicationContextSpec{
|
||||
ApplicationRevisionName: appRevision.GetName(),
|
||||
},
|
||||
}
|
||||
if metav1.GetControllerOf(&appContext) == nil {
|
||||
for i, owner := range appContext.GetOwnerReferences() {
|
||||
if owner.Kind == v1alpha2.ApplicationKind {
|
||||
appContext.GetOwnerReferences()[i].Controller = pointer.BoolPtr(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
// set the AC as rolling
|
||||
oamutil.AddAnnotations(&appContext, map[string]string{oam.AnnotationAppRollout: strconv.FormatBool(true)})
|
||||
// pass the rolling component to the app
|
||||
if len(componentList) != 0 {
|
||||
oamutil.AddAnnotations(&appContext, map[string]string{
|
||||
oam.AnnotationRollingComponent: strings.Join(componentList, common.RollingComponentsSep)})
|
||||
}
|
||||
err := r.Create(ctx, &appContext)
|
||||
return appContext, err
|
||||
}
|
||||
|
||||
// extractWorkloads extracts the workloads from the source and target applicationConfig
|
||||
func (r *Reconciler) extractWorkloads(ctx context.Context, componentList []string, targetAppRevision,
|
||||
sourceAppRevision *v1alpha2.ApplicationRevision) (*unstructured.Unstructured, *unstructured.Unstructured, error) {
|
||||
var componentName string
|
||||
var sourceApp *v1alpha2.ApplicationConfiguration
|
||||
targetApp, err := oamutil.RawExtension2AppConfig(targetAppRevision.Spec.ApplicationConfiguration)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if sourceAppRevision != nil {
|
||||
sourceApp, err = oamutil.RawExtension2AppConfig(sourceAppRevision.Spec.ApplicationConfiguration)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if len(componentList) == 0 {
|
||||
// we need to find a default component
|
||||
commons := appUtil.FindCommonComponent(targetApp, sourceApp)
|
||||
if len(commons) != 1 {
|
||||
return nil, nil, fmt.Errorf("cannot find a default component, too many common components: %+v", commons)
|
||||
}
|
||||
componentName = commons[0]
|
||||
} else {
|
||||
// assume that the validator webhook has already guaranteed that there is no more than one component for now
|
||||
// and the component exists in both the target and source app
|
||||
componentName = componentList[0]
|
||||
}
|
||||
// get the workload definition
|
||||
// the validator webhook has checked that source and the target are the same type
|
||||
targetWorkload, err := r.fetchWorkload(ctx, componentName, targetApp)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
klog.InfoS("successfully get the target workload we need to work on", "targetWorkload", klog.KObj(targetWorkload))
|
||||
if sourceApp != nil {
|
||||
sourceWorkload, err := r.fetchWorkload(ctx, componentName, sourceApp)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
klog.InfoS("successfully get the source workload we need to work on", "sourceWorkload",
|
||||
klog.KObj(sourceWorkload))
|
||||
return targetWorkload, sourceWorkload, nil
|
||||
}
|
||||
return targetWorkload, nil, nil
|
||||
}
|
||||
|
||||
// fetchWorkload based on the component and the appConfig
|
||||
func (r *Reconciler) fetchWorkload(ctx context.Context, componentName string,
|
||||
targetApp *v1alpha2.ApplicationConfiguration) (*unstructured.Unstructured, error) {
|
||||
var targetAcc *v1alpha2.ApplicationConfigurationComponent
|
||||
for _, acc := range targetApp.Spec.Components {
|
||||
if utils.ExtractComponentName(acc.RevisionName) == componentName {
|
||||
targetAcc = acc.DeepCopy()
|
||||
}
|
||||
}
|
||||
// can't happen as we just searched the appConfig
|
||||
if targetAcc == nil {
|
||||
klog.Error("The component does not belong to the application",
|
||||
"components", targetApp.Spec.Components, "component to upgrade", componentName)
|
||||
return nil, fmt.Errorf("the component %s does not belong to the application with components %+v",
|
||||
componentName, targetApp.Spec.Components)
|
||||
}
|
||||
revision, err := utils.ExtractRevision(targetAcc.RevisionName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to get revision given revision name %s",
|
||||
targetAcc.RevisionName))
|
||||
}
|
||||
|
||||
// get the component given the component revision
|
||||
component, _, err := oamutil.GetComponent(ctx, r, *targetAcc, targetApp.GetNamespace())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to get component given its revision %s",
|
||||
targetAcc.RevisionName))
|
||||
}
|
||||
// get the workload template in the component
|
||||
w, err := oamutil.RawExtension2Unstructured(&component.Spec.Workload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to get component given revision %s", targetAcc.RevisionName))
|
||||
}
|
||||
// reuse the same appConfig controller logic that determines the workload name given an ACC
|
||||
// inplaceUpgrade not used in rollout now
|
||||
applicationconfiguration.SetAppWorkloadInstanceName(componentName, w, revision, "")
|
||||
// get the real workload object from api-server given GVK and name
|
||||
workload, err := oamutil.GetObjectGivenGVKAndName(ctx, r, w.GroupVersionKind(), targetApp.GetNamespace(), w.GetName())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to get workload %s with gvk %+v ", w.GetName(), w.GroupVersionKind()))
|
||||
}
|
||||
|
||||
return workload, nil
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
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 applicationrollout
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/openkruise/kruise-api/apps/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/assemble"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
func rolloutWorkloadName() assemble.WorkloadOption {
|
||||
return assemble.WorkloadOptionFn(func(w *unstructured.Unstructured, component *v1alpha2.Component, definition *v1beta1.ComponentDefinition) error {
|
||||
// we hard code the behavior depends on the workload group/kind for now. The only in-place upgradable resources
|
||||
// we support is cloneset/statefulset for now. We can easily add more later.
|
||||
if w.GroupVersionKind().Group == v1alpha1.GroupVersion.Group {
|
||||
if w.GetKind() == reflect.TypeOf(v1alpha1.CloneSet{}).Name() ||
|
||||
w.GetKind() == reflect.TypeOf(v1alpha1.StatefulSet{}).Name() {
|
||||
// we use the component name alone for those resources that do support in-place upgrade
|
||||
klog.InfoS("we reuse the component name for resources that support in-place upgrade",
|
||||
"GVK", w.GroupVersionKind(), "instance name", component.Name)
|
||||
w.SetName(component.Name)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// we assume that the rest of the resources do not support in-place upgrade
|
||||
compRevName := w.GetLabels()[oam.LabelAppComponentRevision]
|
||||
w.SetName(compRevName)
|
||||
klog.InfoS("we encountered an unknown resources, assume that it does not support in-place upgrade",
|
||||
"GVK", w.GroupVersionKind(), "instance name", compRevName)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// appRollout should take over updating workload, so disable previous controller owner(resourceTracker)
|
||||
func disableControllerOwner(workload *unstructured.Unstructured) {
|
||||
if workload == nil {
|
||||
return
|
||||
}
|
||||
ownerRefs := workload.GetOwnerReferences()
|
||||
for i, ref := range ownerRefs {
|
||||
if ref.Controller != nil && *ref.Controller {
|
||||
ownerRefs[i].Controller = pointer.BoolPtr(false)
|
||||
}
|
||||
}
|
||||
workload.SetOwnerReferences(ownerRefs)
|
||||
}
|
||||
|
||||
// enableControllerOwner yield controller owner back to resourceTracker
|
||||
func enableControllerOwner(workload *unstructured.Unstructured) {
|
||||
owners := workload.GetOwnerReferences()
|
||||
for i, owner := range owners {
|
||||
if owner.Kind == v1beta1.ResourceTrackerKind && owner.Controller != nil && !*owner.Controller {
|
||||
owners[i].Controller = pointer.BoolPtr(true)
|
||||
}
|
||||
}
|
||||
workload.SetOwnerReferences(owners)
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
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 applicationrollout
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
|
||||
"gotest.tools/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
func TestDisableControllerOwner(t *testing.T) {
|
||||
w := &unstructured.Unstructured{}
|
||||
owners := []metav1.OwnerReference{
|
||||
{Name: "test-1", Controller: pointer.BoolPtr(false)},
|
||||
{Name: "test-2", Controller: pointer.BoolPtr(true)},
|
||||
}
|
||||
w.SetOwnerReferences(owners)
|
||||
disableControllerOwner(w)
|
||||
assert.Equal(t, 2, len(w.GetOwnerReferences()))
|
||||
for _, reference := range w.GetOwnerReferences() {
|
||||
assert.Equal(t, false, *reference.Controller)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnableControllerOwner(t *testing.T) {
|
||||
w := &unstructured.Unstructured{}
|
||||
owners := []metav1.OwnerReference{
|
||||
{Name: "test-1", Controller: pointer.BoolPtr(false), Kind: v1beta1.ResourceTrackerKind},
|
||||
{Name: "test-2", Controller: pointer.BoolPtr(false), Kind: v1alpha2.ApplicationKind},
|
||||
}
|
||||
w.SetOwnerReferences(owners)
|
||||
enableControllerOwner(w)
|
||||
assert.Equal(t, 2, len(w.GetOwnerReferences()))
|
||||
for _, reference := range w.GetOwnerReferences() {
|
||||
if reference.Kind == v1beta1.ResourceTrackerKind {
|
||||
assert.Equal(t, true, *reference.Controller)
|
||||
} else {
|
||||
assert.Equal(t, false, *reference.Controller)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
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 applicationrollout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/dispatch"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/assemble"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
appUtil "github.com/oam-dev/kubevela/pkg/webhook/core.oam.dev/v1alpha2/applicationrollout"
|
||||
)
|
||||
|
||||
type rolloutHandler struct {
|
||||
*Reconciler
|
||||
appRollout *v1beta1.AppRollout
|
||||
|
||||
// source/targetRevName represent this round reconcile using source and target revision
|
||||
// in most cases they are equal to appRollout.spec.target/sourceRevName but if roll forward or revert in middle of rollout
|
||||
// source/targetRevName are equal to previous rollout
|
||||
sourceRevName string
|
||||
targetRevName string
|
||||
|
||||
sourceAppRevision *v1beta1.ApplicationRevision
|
||||
targetAppRevision *v1beta1.ApplicationRevision
|
||||
|
||||
// sourceWorkloads is assembled by appRevision in assemble phase
|
||||
// please be aware that they are not real status in k8s, they are just generate from appRevision include GVK+namespace+name
|
||||
sourceWorkloads map[string]*unstructured.Unstructured
|
||||
targetWorkloads map[string]*unstructured.Unstructured
|
||||
|
||||
// targetManifests used by dispatch(template targetRevision) and handleSucceed(GC) phase
|
||||
targetManifests []*unstructured.Unstructured
|
||||
|
||||
// sourceManifests used by dispatch(template targetRevision) and handleSucceed(GC) phase
|
||||
sourceManifests []*unstructured.Unstructured
|
||||
|
||||
// needRollComponent is find common component between source and target revision
|
||||
needRollComponent string
|
||||
}
|
||||
|
||||
// prepareRollout call assemble func to prepare info needed in whole reconcile loop
|
||||
func (h *rolloutHandler) prepareRollout(ctx context.Context) error {
|
||||
var err error
|
||||
h.targetAppRevision = new(v1beta1.ApplicationRevision)
|
||||
if err := h.Get(ctx, types.NamespacedName{Namespace: h.appRollout.Namespace, Name: h.targetRevName}, h.targetAppRevision); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// construct a assemble manifest for targetAppRevision
|
||||
targetAssemble := assemble.NewAppManifests(h.targetAppRevision).
|
||||
WithWorkloadOption(rolloutWorkloadName()).
|
||||
WithWorkloadOption(assemble.PrepareWorkloadForRollout())
|
||||
|
||||
// in template phase, we should use targetManifests including target workloads/traits to
|
||||
h.targetManifests, err = targetAssemble.AssembledManifests()
|
||||
if err != nil {
|
||||
klog.Error("appRollout targetAppRevision failed to assemble manifest", "appRollout", klog.KRef(h.appRollout.Namespace, h.appRollout.Name))
|
||||
return err
|
||||
}
|
||||
|
||||
// we only use workloads group by component name to find common workloads in source and target revision
|
||||
h.targetWorkloads, _, _, err = targetAssemble.GroupAssembledManifests()
|
||||
if err != nil {
|
||||
klog.Error("appRollout targetAppRevision failed to assemble target workload", "appRollout", klog.KRef(h.appRollout.Namespace, h.appRollout.Name))
|
||||
return err
|
||||
}
|
||||
|
||||
if len(h.sourceRevName) != 0 {
|
||||
h.sourceAppRevision = new(v1beta1.ApplicationRevision)
|
||||
if err := h.Get(ctx, types.NamespacedName{Namespace: h.appRollout.Namespace, Name: h.sourceRevName}, h.sourceAppRevision); err != nil {
|
||||
return err
|
||||
}
|
||||
// construct a assemble manifest for sourceAppRevision
|
||||
sourceAssemble := assemble.NewAppManifests(h.sourceAppRevision).
|
||||
WithWorkloadOption(assemble.PrepareWorkloadForRollout()).
|
||||
WithWorkloadOption(rolloutWorkloadName())
|
||||
h.sourceWorkloads, _, _, err = sourceAssemble.GroupAssembledManifests()
|
||||
if err != nil {
|
||||
klog.Error("appRollout sourceAppRevision failed to assemble workloads", "appRollout", klog.KRef(h.appRollout.Namespace, h.appRollout.Name))
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// we only support one workload now, so this func is to determine witch component is need to rollout
|
||||
func (h *rolloutHandler) determineRolloutComponent() error {
|
||||
componentList := h.appRollout.Spec.ComponentList
|
||||
// if user not set ComponentList in AppRollout we also find a common component between source and target
|
||||
if len(componentList) == 0 {
|
||||
// we need to find a default component
|
||||
commons := appUtil.FindCommonComponentWithManifest(h.targetWorkloads, h.sourceWorkloads)
|
||||
if len(commons) != 1 {
|
||||
return fmt.Errorf("cannot find a default component, too many common components: %+v", commons)
|
||||
}
|
||||
h.needRollComponent = commons[0]
|
||||
} else {
|
||||
// assume that the validator webhook has already guaranteed that there is no more than one component for now
|
||||
// and the component exists in both the target and source app
|
||||
h.needRollComponent = componentList[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetch source and target workload
|
||||
func (h *rolloutHandler) fetchSourceAndTargetWorkload(ctx context.Context) (*unstructured.Unstructured, *unstructured.Unstructured, error) {
|
||||
var sourceWorkload, targetWorkload *unstructured.Unstructured
|
||||
var err error
|
||||
if len(h.sourceRevName) == 0 {
|
||||
klog.Info("source app fields not filled, this is a scale operation")
|
||||
} else if sourceWorkload, err = h.extractWorkload(ctx, *h.sourceWorkloads[h.needRollComponent]); err != nil {
|
||||
klog.Errorf("specified sourceRevName but cannot fetch source workload %s: %v",
|
||||
h.appRollout.Spec.SourceAppRevisionName, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
if targetWorkload, err = h.extractWorkload(ctx, *h.targetWorkloads[h.needRollComponent]); err != nil {
|
||||
klog.Errorf("cannot fetch target workload %s: %v", h.appRollout.Spec.TargetAppRevisionName, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
return sourceWorkload, targetWorkload, nil
|
||||
}
|
||||
|
||||
// extractWorkload use GVK and name of workload(assembled result) to fetch real workload in cluster
|
||||
func (h *rolloutHandler) extractWorkload(ctx context.Context, workload unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
wl, err := oamutil.GetObjectGivenGVKAndName(ctx, h, workload.GroupVersionKind(), workload.GetNamespace(), workload.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wl, nil
|
||||
}
|
||||
|
||||
// if in middle of previous rollout, continue use previous source and target appRevision as this round rollout
|
||||
func (h *rolloutHandler) handleRolloutModified() {
|
||||
klog.InfoS("rollout target changed, restart the rollout", "new source", h.appRollout.Spec.SourceAppRevisionName,
|
||||
"new target", h.appRollout.Spec.TargetAppRevisionName)
|
||||
h.record.Event(h.appRollout, event.Normal("Rollout Restarted",
|
||||
"rollout target changed, restart the rollout", "new source", h.appRollout.Spec.SourceAppRevisionName,
|
||||
"new target", h.appRollout.Spec.TargetAppRevisionName))
|
||||
// we are okay to move directly to restart the rollout since we are at the terminal state
|
||||
// however, we need to make sure we properly finalizing the existing rollout before restart if it's
|
||||
// still in the middle of rolling out
|
||||
if h.appRollout.Status.RollingState != v1alpha1.RolloutSucceedState &&
|
||||
h.appRollout.Status.RollingState != v1alpha1.RolloutFailedState {
|
||||
// happen when roll forward or revert in middle of rollout, previous rollout haven't finished
|
||||
// continue to handle the previous resources until we are okay to move forward
|
||||
h.targetRevName = h.appRollout.Status.LastUpgradedTargetAppRevision
|
||||
h.sourceRevName = h.appRollout.Status.LastSourceAppRevision
|
||||
} else {
|
||||
// previous rollout have finished, go ahead using new source/target revision
|
||||
h.targetRevName = h.appRollout.Spec.TargetAppRevisionName
|
||||
h.sourceRevName = h.appRollout.Spec.SourceAppRevisionName
|
||||
// mark so that we don't think we are modified again
|
||||
h.appRollout.Status.LastUpgradedTargetAppRevision = h.appRollout.Spec.TargetAppRevisionName
|
||||
h.appRollout.Status.LastSourceAppRevision = h.appRollout.Spec.SourceAppRevisionName
|
||||
}
|
||||
h.appRollout.Status.StateTransition(v1alpha1.RollingModifiedEvent)
|
||||
}
|
||||
|
||||
// templateTargetManifest call dispatch to template target app revision's manifests to cluster
|
||||
func (h *rolloutHandler) templateTargetManifest(ctx context.Context) error {
|
||||
var rt *v1beta1.ResourceTracker
|
||||
// if sourceAppRevision is not nil, we should upgrade existing resources which are also needed by target app
|
||||
// revision
|
||||
if h.sourceAppRevision != nil {
|
||||
rt = new(v1beta1.ResourceTracker)
|
||||
err := h.Get(ctx, types.NamespacedName{Name: dispatch.ConstructResourceTrackerName(h.appRollout.Spec.SourceAppRevisionName, h.appRollout.Namespace)}, rt)
|
||||
if err != nil {
|
||||
klog.Errorf("specified sourceAppRevisionName %s but cannot fetch the sourceResourceTracker %v",
|
||||
h.appRollout.Spec.SourceAppRevisionName, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// use source resourceTracker to handle same resource owner transfer
|
||||
dispatcher := dispatch.NewAppManifestsDispatcher(h, h.targetAppRevision).EnableUpgradeAndSkipGC(rt)
|
||||
_, err := dispatcher.Dispatch(ctx, h.targetManifests)
|
||||
if err != nil {
|
||||
klog.Errorf("dispatch targetRevision error %s:%v", h.appRollout.Spec.TargetAppRevisionName, err)
|
||||
return err
|
||||
}
|
||||
workload, err := h.extractWorkload(ctx, *h.targetWorkloads[h.needRollComponent])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ref := metav1.GetControllerOfNoCopy(workload)
|
||||
if ref != nil && ref.Kind == v1beta1.ResourceTrackerKind {
|
||||
wlPatch := client.MergeFrom(workload.DeepCopy())
|
||||
// guarantee resourceTracker isn't controller owner of workload
|
||||
disableControllerOwner(workload)
|
||||
if err = h.Client.Patch(ctx, workload, wlPatch, client.FieldOwner(h.appRollout.UID)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// templateTargetManifest call dispatch to template source app revision's manifests to cluster
|
||||
func (h *rolloutHandler) templateSourceManifest(ctx context.Context) error {
|
||||
|
||||
// only when sourceAppRevision is not nil, we need template sourceRevision revision
|
||||
if h.sourceAppRevision == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// use source resourceTracker to handle same resource owner transfer
|
||||
dispatcher := dispatch.NewAppManifestsDispatcher(h, h.sourceAppRevision)
|
||||
_, err := dispatcher.Dispatch(ctx, h.sourceManifests)
|
||||
if err != nil {
|
||||
klog.Errorf("dispatch sourceRevision error %s:%v", h.appRollout.Spec.TargetAppRevisionName, err)
|
||||
return err
|
||||
}
|
||||
workload, err := h.extractWorkload(ctx, *h.sourceWorkloads[h.needRollComponent])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ref := metav1.GetControllerOfNoCopy(workload)
|
||||
if ref != nil && ref.Kind == v1beta1.ResourceTrackerKind {
|
||||
wlPatch := client.MergeFrom(workload.DeepCopy())
|
||||
// guarantee resourceTracker isn't controller owner of workload
|
||||
disableControllerOwner(workload)
|
||||
if err = h.Client.Patch(ctx, workload, wlPatch, client.FieldOwner(h.appRollout.UID)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handle rollout succeed work left
|
||||
func (h *rolloutHandler) finalizeRollingSucceeded(ctx context.Context) error {
|
||||
// yield controller owner back to resourceTracker
|
||||
workload, err := h.extractWorkload(ctx, *h.targetWorkloads[h.needRollComponent])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wlPatch := client.MergeFrom(workload.DeepCopy())
|
||||
enableControllerOwner(workload)
|
||||
if err = h.Client.Patch(ctx, workload, wlPatch, client.FieldOwner(h.appRollout.UID)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// only when sourceAppRevision is not nil, we need gc old revision resources
|
||||
if h.sourceAppRevision != nil {
|
||||
oldRT := &v1beta1.ResourceTracker{}
|
||||
err := h.Client.Get(ctx, client.ObjectKey{
|
||||
Name: dispatch.ConstructResourceTrackerName(h.sourceAppRevision.Name, h.sourceAppRevision.Namespace)}, oldRT)
|
||||
if err != nil && apierrors.IsNotFound(err) {
|
||||
// end finalizing if source revision's tracker is already gone
|
||||
// this guarantees finalizeRollingSucceeded will only GC once
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d := dispatch.NewAppManifestsDispatcher(h.Client, h.targetAppRevision).
|
||||
EnableUpgradeAndGC(oldRT)
|
||||
// no need to dispatch manifest again, just do GC
|
||||
if _, err := d.Dispatch(ctx, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user