Files
kubevela/pkg/controller/core.oam.dev/v1alpha2/applicationrollout/helper.go
T
wyike ff6fcd9f10 support component revision based rollout (#1919)
* 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
2021-07-20 16:53:39 +08:00

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))
}