mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-23 22:46:53 +00:00
* WIP almost finish main logic WIP make reviewable finish manual test fix rollout plan related test WIP refactro some logic WIP finish e2e-test * fix failed e2e-test * fix failed interagtion test * move rollout to trait packege * modify e2e-test testdata change apiVersion * add v1alpha1 to scheme in test * more wait time * fix comment * split rollout test fix makefile * rename test name delete equal quata try to fix fix * add more test
183 lines
7.6 KiB
Go
183 lines
7.6 KiB
Go
/*
|
|
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"
|
|
"reflect"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/utils/pointer"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
|
|
"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/v1beta1"
|
|
oamstd "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
|
|
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/assemble"
|
|
"github.com/oam-dev/kubevela/pkg/oam"
|
|
)
|
|
|
|
// RolloutWorkloadName generate workload name for when rollout
|
|
func RolloutWorkloadName(rolloutComp string) assemble.WorkloadOption {
|
|
return assemble.WorkloadOptionFn(func(w *unstructured.Unstructured, _ *v1beta1.ComponentDefinition, _ []*unstructured.Unstructured) error {
|
|
|
|
compName := w.GetLabels()[oam.LabelAppComponent]
|
|
if compName != rolloutComp {
|
|
return nil
|
|
}
|
|
|
|
// 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", w.GetName())
|
|
// assemble use component name as workload name by default
|
|
// so no need to re-set 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
|
|
})
|
|
}
|
|
|
|
// HandleReplicas override initial replicas of workload
|
|
func HandleReplicas(ctx context.Context, rolloutComp string, c client.Client) assemble.WorkloadOption {
|
|
return assemble.WorkloadOptionFn(func(u *unstructured.Unstructured, _ *v1beta1.ComponentDefinition, _ []*unstructured.Unstructured) error {
|
|
compName := u.GetLabels()[oam.LabelAppComponent]
|
|
if compName != rolloutComp {
|
|
return nil
|
|
}
|
|
|
|
pv := fieldpath.Pave(u.UnstructuredContent())
|
|
|
|
// we hard code here, but we can easily support more types of workload by add more cases logic in switch
|
|
var replicasFieldPath string
|
|
switch u.GetKind() {
|
|
case reflect.TypeOf(v1alpha1.CloneSet{}).Name(), reflect.TypeOf(appsv1.Deployment{}).Name():
|
|
replicasFieldPath = "spec.replicas"
|
|
default:
|
|
klog.Errorf("rollout meet a workload we cannot support yet", "Kind", u.GetKind(), "name", u.GetName())
|
|
return fmt.Errorf("rollout meet a workload we cannot support yet Kind %s name %s", u.GetKind(), u.GetName())
|
|
}
|
|
|
|
workload := u.DeepCopy()
|
|
if err := c.Get(ctx, types.NamespacedName{Namespace: u.GetNamespace(), Name: u.GetName()}, workload); err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
// workload not exist(eg. first scale operation) we force set the replicas to zero
|
|
err = pv.SetNumber(replicasFieldPath, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
klog.InfoS("assemble force set workload replicas to 0", "Kind", u.GetKind(), "name", u.GetName())
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
// the workload already exist, we cannot reset the replicas with manifest
|
|
// eg. if workload type is cloneset. the source worklaod and target worklaod is same one
|
|
// so dispatch shouldn't modify current replica number.
|
|
wlpv := fieldpath.Pave(workload.UnstructuredContent())
|
|
replicas, err := wlpv.GetInteger(replicasFieldPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = pv.SetNumber(replicasFieldPath, float64(replicas)); err != nil {
|
|
return err
|
|
}
|
|
klog.InfoS("assemble set existing workload replicas", "Kind", u.GetKind(), "name", u.GetName(), "replicas", replicas)
|
|
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)
|
|
}
|
|
|
|
func handleRollingTerminated(appRollout v1beta1.AppRollout) bool {
|
|
// handle rollout completed
|
|
if appRollout.Status.RollingState == oamstd.RolloutSucceedState ||
|
|
appRollout.Status.RollingState == oamstd.RolloutFailedState {
|
|
if appRollout.Status.LastUpgradedTargetAppRevision == appRollout.Spec.TargetAppRevisionName &&
|
|
appRollout.Status.LastSourceAppRevision == appRollout.Spec.SourceAppRevisionName {
|
|
// spec.targetSize could be nil, If targetSize isn't nil and not equal to status.RolloutTargetSize it's
|
|
// means user have modified targetSize to restart an scale operation
|
|
if appRollout.Spec.RolloutPlan.TargetSize != nil {
|
|
if appRollout.Status.RolloutTargetSize == *appRollout.Spec.RolloutPlan.TargetSize {
|
|
klog.InfoS("rollout completed, no need to reconcile", "source", appRollout.Spec.SourceAppRevisionName,
|
|
"target", appRollout.Spec.TargetAppRevisionName)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
klog.InfoS("rollout completed, no need to reconcile", "source", appRollout.Spec.SourceAppRevisionName,
|
|
"target", appRollout.Spec.TargetAppRevisionName)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// check if either the source or the target of the appRollout has changed.
|
|
// when reset the state machine, the controller will set the status.RolloutTargetSize as -1 in AppLocating phase
|
|
// so we should ignore this case.
|
|
// if status.RolloutTargetSize isn't equal to Spec.RolloutPlan.TargetSize, it's means user want trigger another scale operation.
|
|
func isRolloutModified(appRollout v1beta1.AppRollout) bool {
|
|
return appRollout.Status.RollingState != oamstd.RolloutDeletingState &&
|
|
((appRollout.Status.LastUpgradedTargetAppRevision != "" &&
|
|
appRollout.Status.LastUpgradedTargetAppRevision != appRollout.Spec.TargetAppRevisionName) ||
|
|
(appRollout.Status.LastSourceAppRevision != "" &&
|
|
appRollout.Status.LastSourceAppRevision != appRollout.Spec.SourceAppRevisionName) ||
|
|
(appRollout.Spec.RolloutPlan.TargetSize != nil && appRollout.Status.RolloutTargetSize != -1 &&
|
|
appRollout.Status.RolloutTargetSize != *appRollout.Spec.RolloutPlan.TargetSize))
|
|
}
|