new v1beta1 apis (#184)
Signed-off-by: liheng.zms <liheng.zms@alibaba-inc.com>
This commit is contained in:
parent
07b7f20f6a
commit
9dcf3659d2
|
|
@ -0,0 +1,458 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Kruise 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 v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/openkruise/rollouts/api/v1beta1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
utilpointer "k8s.io/utils/pointer"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/conversion"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (src *Rollout) ConvertTo(dst conversion.Hub) error {
|
||||||
|
switch t := dst.(type) {
|
||||||
|
case *v1beta1.Rollout:
|
||||||
|
obj := dst.(*v1beta1.Rollout)
|
||||||
|
obj.ObjectMeta = src.ObjectMeta
|
||||||
|
obj.Spec = v1beta1.RolloutSpec{}
|
||||||
|
srcSpec := src.Spec
|
||||||
|
obj.Spec.WorkloadRef = v1beta1.ObjectRef{
|
||||||
|
APIVersion: srcSpec.ObjectRef.WorkloadRef.APIVersion,
|
||||||
|
Kind: srcSpec.ObjectRef.WorkloadRef.Kind,
|
||||||
|
Name: srcSpec.ObjectRef.WorkloadRef.Name,
|
||||||
|
}
|
||||||
|
obj.Spec.Disabled = srcSpec.Disabled
|
||||||
|
obj.Spec.Strategy = v1beta1.RolloutStrategy{
|
||||||
|
Paused: srcSpec.Strategy.Paused,
|
||||||
|
Canary: &v1beta1.CanaryStrategy{
|
||||||
|
FailureThreshold: srcSpec.Strategy.Canary.FailureThreshold,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, step := range srcSpec.Strategy.Canary.Steps {
|
||||||
|
o := v1beta1.CanaryStep{
|
||||||
|
TrafficRoutingStrategy: ConversionToV1beta1TrafficRoutingStrategy(step.TrafficRoutingStrategy),
|
||||||
|
Replicas: step.Replicas,
|
||||||
|
Pause: v1beta1.RolloutPause{Duration: step.Pause.Duration},
|
||||||
|
}
|
||||||
|
if step.Replicas == nil && step.Weight != nil {
|
||||||
|
o.Replicas = &intstr.IntOrString{
|
||||||
|
Type: intstr.String,
|
||||||
|
StrVal: fmt.Sprintf("%d", *step.Weight) + "%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.Spec.Strategy.Canary.Steps = append(obj.Spec.Strategy.Canary.Steps, o)
|
||||||
|
}
|
||||||
|
for _, ref := range srcSpec.Strategy.Canary.TrafficRoutings {
|
||||||
|
o := ConversionToV1beta1TrafficRoutingRef(ref)
|
||||||
|
obj.Spec.Strategy.Canary.TrafficRoutings = append(obj.Spec.Strategy.Canary.TrafficRoutings, o)
|
||||||
|
}
|
||||||
|
if srcSpec.Strategy.Canary.PatchPodTemplateMetadata != nil {
|
||||||
|
obj.Spec.Strategy.Canary.PatchPodTemplateMetadata = &v1beta1.PatchPodTemplateMetadata{
|
||||||
|
Annotations: map[string]string{},
|
||||||
|
Labels: map[string]string{},
|
||||||
|
}
|
||||||
|
for k, v := range srcSpec.Strategy.Canary.PatchPodTemplateMetadata.Annotations {
|
||||||
|
obj.Spec.Strategy.Canary.PatchPodTemplateMetadata.Annotations[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range srcSpec.Strategy.Canary.PatchPodTemplateMetadata.Labels {
|
||||||
|
obj.Spec.Strategy.Canary.PatchPodTemplateMetadata.Labels[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if src.Annotations[RolloutStyleAnnotation] != string(PartitionRollingStyle) {
|
||||||
|
obj.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true
|
||||||
|
}
|
||||||
|
if src.Annotations[TrafficRoutingAnnotation] != "" {
|
||||||
|
obj.Spec.Strategy.Canary.TrafficRoutingRef = src.Annotations[TrafficRoutingAnnotation]
|
||||||
|
}
|
||||||
|
|
||||||
|
// status
|
||||||
|
obj.Status = v1beta1.RolloutStatus{
|
||||||
|
ObservedGeneration: src.Status.ObservedGeneration,
|
||||||
|
Phase: v1beta1.RolloutPhase(src.Status.Phase),
|
||||||
|
Message: src.Status.Message,
|
||||||
|
}
|
||||||
|
for _, cond := range src.Status.Conditions {
|
||||||
|
o := v1beta1.RolloutCondition{
|
||||||
|
Type: v1beta1.RolloutConditionType(cond.Type),
|
||||||
|
Status: cond.Status,
|
||||||
|
LastUpdateTime: cond.LastUpdateTime,
|
||||||
|
LastTransitionTime: cond.LastTransitionTime,
|
||||||
|
Reason: cond.Reason,
|
||||||
|
Message: cond.Message,
|
||||||
|
}
|
||||||
|
obj.Status.Conditions = append(obj.Status.Conditions, o)
|
||||||
|
}
|
||||||
|
if src.Status.CanaryStatus == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
obj.Status.CanaryStatus = &v1beta1.CanaryStatus{
|
||||||
|
ObservedWorkloadGeneration: src.Status.CanaryStatus.ObservedWorkloadGeneration,
|
||||||
|
ObservedRolloutID: src.Status.CanaryStatus.ObservedRolloutID,
|
||||||
|
RolloutHash: src.Status.CanaryStatus.RolloutHash,
|
||||||
|
StableRevision: src.Status.CanaryStatus.StableRevision,
|
||||||
|
CanaryRevision: src.Status.CanaryStatus.CanaryRevision,
|
||||||
|
PodTemplateHash: src.Status.CanaryStatus.PodTemplateHash,
|
||||||
|
CanaryReplicas: src.Status.CanaryStatus.CanaryReplicas,
|
||||||
|
CanaryReadyReplicas: src.Status.CanaryStatus.CanaryReadyReplicas,
|
||||||
|
CurrentStepIndex: src.Status.CanaryStatus.CurrentStepIndex,
|
||||||
|
CurrentStepState: v1beta1.CanaryStepState(src.Status.CanaryStatus.CurrentStepState),
|
||||||
|
Message: src.Status.CanaryStatus.Message,
|
||||||
|
LastUpdateTime: src.Status.CanaryStatus.LastUpdateTime,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type %v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConversionToV1beta1TrafficRoutingRef(src TrafficRoutingRef) (dst v1beta1.TrafficRoutingRef) {
|
||||||
|
dst.Service = src.Service
|
||||||
|
dst.GracePeriodSeconds = src.GracePeriodSeconds
|
||||||
|
if src.Ingress != nil {
|
||||||
|
dst.Ingress = &v1beta1.IngressTrafficRouting{
|
||||||
|
ClassType: src.Ingress.ClassType,
|
||||||
|
Name: src.Ingress.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if src.Gateway != nil {
|
||||||
|
dst.Gateway = &v1beta1.GatewayTrafficRouting{
|
||||||
|
HTTPRouteName: src.Gateway.HTTPRouteName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ref := range src.CustomNetworkRefs {
|
||||||
|
obj := v1beta1.ObjectRef{
|
||||||
|
APIVersion: ref.APIVersion,
|
||||||
|
Kind: ref.Kind,
|
||||||
|
Name: ref.Name,
|
||||||
|
}
|
||||||
|
dst.CustomNetworkRefs = append(dst.CustomNetworkRefs, obj)
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConversionToV1beta1TrafficRoutingStrategy(src TrafficRoutingStrategy) (dst v1beta1.TrafficRoutingStrategy) {
|
||||||
|
if src.Weight != nil {
|
||||||
|
dst.Traffic = utilpointer.StringPtr(fmt.Sprintf("%d", *src.Weight) + "%")
|
||||||
|
}
|
||||||
|
dst.RequestHeaderModifier = src.RequestHeaderModifier
|
||||||
|
for _, match := range src.Matches {
|
||||||
|
obj := v1beta1.HttpRouteMatch{
|
||||||
|
Headers: match.Headers,
|
||||||
|
}
|
||||||
|
dst.Matches = append(dst.Matches, obj)
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dst *Rollout) ConvertFrom(src conversion.Hub) error {
|
||||||
|
switch t := src.(type) {
|
||||||
|
case *v1beta1.Rollout:
|
||||||
|
srcV1beta1 := src.(*v1beta1.Rollout)
|
||||||
|
dst.ObjectMeta = srcV1beta1.ObjectMeta
|
||||||
|
|
||||||
|
// spec
|
||||||
|
dst.Spec = RolloutSpec{
|
||||||
|
ObjectRef: ObjectRef{
|
||||||
|
WorkloadRef: &WorkloadRef{
|
||||||
|
APIVersion: srcV1beta1.Spec.WorkloadRef.APIVersion,
|
||||||
|
Kind: srcV1beta1.Spec.WorkloadRef.Kind,
|
||||||
|
Name: srcV1beta1.Spec.WorkloadRef.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Strategy: RolloutStrategy{
|
||||||
|
Paused: srcV1beta1.Spec.Strategy.Paused,
|
||||||
|
Canary: &CanaryStrategy{
|
||||||
|
FailureThreshold: srcV1beta1.Spec.Strategy.Canary.FailureThreshold,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Disabled: srcV1beta1.Spec.Disabled,
|
||||||
|
}
|
||||||
|
for _, step := range srcV1beta1.Spec.Strategy.Canary.Steps {
|
||||||
|
obj := CanaryStep{
|
||||||
|
TrafficRoutingStrategy: ConversionToV1alpha1TrafficRoutingStrategy(step.TrafficRoutingStrategy),
|
||||||
|
Replicas: step.Replicas,
|
||||||
|
Pause: RolloutPause{Duration: step.Pause.Duration},
|
||||||
|
}
|
||||||
|
dst.Spec.Strategy.Canary.Steps = append(dst.Spec.Strategy.Canary.Steps, obj)
|
||||||
|
}
|
||||||
|
for _, ref := range srcV1beta1.Spec.Strategy.Canary.TrafficRoutings {
|
||||||
|
obj := ConversionToV1alpha1TrafficRoutingRef(ref)
|
||||||
|
dst.Spec.Strategy.Canary.TrafficRoutings = append(dst.Spec.Strategy.Canary.TrafficRoutings, obj)
|
||||||
|
}
|
||||||
|
if srcV1beta1.Spec.Strategy.Canary.PatchPodTemplateMetadata != nil {
|
||||||
|
dst.Spec.Strategy.Canary.PatchPodTemplateMetadata = &PatchPodTemplateMetadata{
|
||||||
|
Annotations: map[string]string{},
|
||||||
|
Labels: map[string]string{},
|
||||||
|
}
|
||||||
|
for k, v := range srcV1beta1.Spec.Strategy.Canary.PatchPodTemplateMetadata.Annotations {
|
||||||
|
dst.Spec.Strategy.Canary.PatchPodTemplateMetadata.Annotations[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range srcV1beta1.Spec.Strategy.Canary.PatchPodTemplateMetadata.Labels {
|
||||||
|
dst.Spec.Strategy.Canary.PatchPodTemplateMetadata.Labels[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dst.Annotations == nil {
|
||||||
|
dst.Annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
if srcV1beta1.Spec.Strategy.Canary.EnableExtraWorkloadForCanary {
|
||||||
|
dst.Annotations[RolloutStyleAnnotation] = string(CanaryRollingStyle)
|
||||||
|
} else {
|
||||||
|
dst.Annotations[RolloutStyleAnnotation] = string(PartitionRollingStyle)
|
||||||
|
}
|
||||||
|
if srcV1beta1.Spec.Strategy.Canary.TrafficRoutingRef != "" {
|
||||||
|
dst.Annotations[TrafficRoutingAnnotation] = srcV1beta1.Spec.Strategy.Canary.TrafficRoutingRef
|
||||||
|
}
|
||||||
|
|
||||||
|
// status
|
||||||
|
dst.Status = RolloutStatus{
|
||||||
|
ObservedGeneration: srcV1beta1.Status.ObservedGeneration,
|
||||||
|
Phase: RolloutPhase(srcV1beta1.Status.Phase),
|
||||||
|
Message: srcV1beta1.Status.Message,
|
||||||
|
}
|
||||||
|
for _, cond := range srcV1beta1.Status.Conditions {
|
||||||
|
obj := RolloutCondition{
|
||||||
|
Type: RolloutConditionType(cond.Type),
|
||||||
|
Status: cond.Status,
|
||||||
|
LastUpdateTime: cond.LastUpdateTime,
|
||||||
|
LastTransitionTime: cond.LastTransitionTime,
|
||||||
|
Reason: cond.Reason,
|
||||||
|
Message: cond.Message,
|
||||||
|
}
|
||||||
|
dst.Status.Conditions = append(dst.Status.Conditions, obj)
|
||||||
|
}
|
||||||
|
if srcV1beta1.Status.CanaryStatus == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dst.Status.CanaryStatus = &CanaryStatus{
|
||||||
|
ObservedWorkloadGeneration: srcV1beta1.Status.CanaryStatus.ObservedWorkloadGeneration,
|
||||||
|
ObservedRolloutID: srcV1beta1.Status.CanaryStatus.ObservedRolloutID,
|
||||||
|
RolloutHash: srcV1beta1.Status.CanaryStatus.RolloutHash,
|
||||||
|
StableRevision: srcV1beta1.Status.CanaryStatus.StableRevision,
|
||||||
|
CanaryRevision: srcV1beta1.Status.CanaryStatus.CanaryRevision,
|
||||||
|
PodTemplateHash: srcV1beta1.Status.CanaryStatus.PodTemplateHash,
|
||||||
|
CanaryReplicas: srcV1beta1.Status.CanaryStatus.CanaryReplicas,
|
||||||
|
CanaryReadyReplicas: srcV1beta1.Status.CanaryStatus.CanaryReadyReplicas,
|
||||||
|
CurrentStepIndex: srcV1beta1.Status.CanaryStatus.CurrentStepIndex,
|
||||||
|
CurrentStepState: CanaryStepState(srcV1beta1.Status.CanaryStatus.CurrentStepState),
|
||||||
|
Message: srcV1beta1.Status.CanaryStatus.Message,
|
||||||
|
LastUpdateTime: srcV1beta1.Status.CanaryStatus.LastUpdateTime,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type %v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConversionToV1alpha1TrafficRoutingStrategy(src v1beta1.TrafficRoutingStrategy) (dst TrafficRoutingStrategy) {
|
||||||
|
if src.Traffic != nil {
|
||||||
|
is := intstr.FromString(*src.Traffic)
|
||||||
|
weight, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
|
||||||
|
dst.Weight = utilpointer.Int32(int32(weight))
|
||||||
|
}
|
||||||
|
dst.RequestHeaderModifier = src.RequestHeaderModifier
|
||||||
|
for _, match := range src.Matches {
|
||||||
|
obj := HttpRouteMatch{
|
||||||
|
Headers: match.Headers,
|
||||||
|
}
|
||||||
|
dst.Matches = append(dst.Matches, obj)
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConversionToV1alpha1TrafficRoutingRef(src v1beta1.TrafficRoutingRef) (dst TrafficRoutingRef) {
|
||||||
|
dst.Service = src.Service
|
||||||
|
dst.GracePeriodSeconds = src.GracePeriodSeconds
|
||||||
|
if src.Ingress != nil {
|
||||||
|
dst.Ingress = &IngressTrafficRouting{
|
||||||
|
ClassType: src.Ingress.ClassType,
|
||||||
|
Name: src.Ingress.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if src.Gateway != nil {
|
||||||
|
dst.Gateway = &GatewayTrafficRouting{
|
||||||
|
HTTPRouteName: src.Gateway.HTTPRouteName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ref := range src.CustomNetworkRefs {
|
||||||
|
obj := CustomNetworkRef{
|
||||||
|
APIVersion: ref.APIVersion,
|
||||||
|
Kind: ref.Kind,
|
||||||
|
Name: ref.Name,
|
||||||
|
}
|
||||||
|
dst.CustomNetworkRefs = append(dst.CustomNetworkRefs, obj)
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func (src *BatchRelease) ConvertTo(dst conversion.Hub) error {
|
||||||
|
switch t := dst.(type) {
|
||||||
|
case *v1beta1.BatchRelease:
|
||||||
|
obj := dst.(*v1beta1.BatchRelease)
|
||||||
|
obj.ObjectMeta = src.ObjectMeta
|
||||||
|
obj.Spec = v1beta1.BatchReleaseSpec{}
|
||||||
|
srcSpec := src.Spec
|
||||||
|
obj.Spec.WorkloadRef = v1beta1.ObjectRef{
|
||||||
|
APIVersion: srcSpec.TargetRef.WorkloadRef.APIVersion,
|
||||||
|
Kind: srcSpec.TargetRef.WorkloadRef.Kind,
|
||||||
|
Name: srcSpec.TargetRef.WorkloadRef.Name,
|
||||||
|
}
|
||||||
|
obj.Spec.ReleasePlan = v1beta1.ReleasePlan{
|
||||||
|
BatchPartition: srcSpec.ReleasePlan.BatchPartition,
|
||||||
|
RolloutID: srcSpec.ReleasePlan.RolloutID,
|
||||||
|
FailureThreshold: srcSpec.ReleasePlan.FailureThreshold,
|
||||||
|
FinalizingPolicy: v1beta1.FinalizingPolicyType(srcSpec.ReleasePlan.FinalizingPolicy),
|
||||||
|
}
|
||||||
|
for _, batch := range srcSpec.ReleasePlan.Batches {
|
||||||
|
o := v1beta1.ReleaseBatch{
|
||||||
|
CanaryReplicas: batch.CanaryReplicas,
|
||||||
|
}
|
||||||
|
obj.Spec.ReleasePlan.Batches = append(obj.Spec.ReleasePlan.Batches, o)
|
||||||
|
}
|
||||||
|
if srcSpec.ReleasePlan.PatchPodTemplateMetadata != nil {
|
||||||
|
obj.Spec.ReleasePlan.PatchPodTemplateMetadata = &v1beta1.PatchPodTemplateMetadata{
|
||||||
|
Annotations: map[string]string{},
|
||||||
|
Labels: map[string]string{},
|
||||||
|
}
|
||||||
|
for k, v := range srcSpec.ReleasePlan.PatchPodTemplateMetadata.Annotations {
|
||||||
|
obj.Spec.ReleasePlan.PatchPodTemplateMetadata.Annotations[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range srcSpec.ReleasePlan.PatchPodTemplateMetadata.Labels {
|
||||||
|
obj.Spec.ReleasePlan.PatchPodTemplateMetadata.Labels[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if src.Annotations[RolloutStyleAnnotation] != string(PartitionRollingStyle) {
|
||||||
|
obj.Spec.ReleasePlan.EnableExtraWorkloadForCanary = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// status
|
||||||
|
obj.Status = v1beta1.BatchReleaseStatus{
|
||||||
|
StableRevision: src.Status.StableRevision,
|
||||||
|
UpdateRevision: src.Status.UpdateRevision,
|
||||||
|
ObservedGeneration: src.Status.ObservedGeneration,
|
||||||
|
ObservedRolloutID: src.Status.ObservedRolloutID,
|
||||||
|
ObservedWorkloadReplicas: src.Status.ObservedWorkloadReplicas,
|
||||||
|
ObservedReleasePlanHash: src.Status.ObservedReleasePlanHash,
|
||||||
|
CollisionCount: src.Status.CollisionCount,
|
||||||
|
Phase: v1beta1.RolloutPhase(src.Status.Phase),
|
||||||
|
}
|
||||||
|
for _, cond := range src.Status.Conditions {
|
||||||
|
o := v1beta1.RolloutCondition{
|
||||||
|
Type: v1beta1.RolloutConditionType(cond.Type),
|
||||||
|
Status: cond.Status,
|
||||||
|
LastUpdateTime: cond.LastUpdateTime,
|
||||||
|
LastTransitionTime: cond.LastTransitionTime,
|
||||||
|
Reason: cond.Reason,
|
||||||
|
Message: cond.Message,
|
||||||
|
}
|
||||||
|
obj.Status.Conditions = append(obj.Status.Conditions, o)
|
||||||
|
}
|
||||||
|
obj.Status.CanaryStatus = v1beta1.BatchReleaseCanaryStatus{
|
||||||
|
CurrentBatchState: v1beta1.BatchReleaseBatchStateType(src.Status.CanaryStatus.CurrentBatchState),
|
||||||
|
CurrentBatch: src.Status.CanaryStatus.CurrentBatch,
|
||||||
|
BatchReadyTime: src.Status.CanaryStatus.BatchReadyTime,
|
||||||
|
UpdatedReplicas: src.Status.CanaryStatus.UpdatedReplicas,
|
||||||
|
UpdatedReadyReplicas: src.Status.CanaryStatus.UpdatedReadyReplicas,
|
||||||
|
NoNeedUpdateReplicas: src.Status.CanaryStatus.NoNeedUpdateReplicas,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type %v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dst *BatchRelease) ConvertFrom(src conversion.Hub) error {
|
||||||
|
switch t := src.(type) {
|
||||||
|
case *v1beta1.BatchRelease:
|
||||||
|
srcV1beta1 := src.(*v1beta1.BatchRelease)
|
||||||
|
dst.ObjectMeta = srcV1beta1.ObjectMeta
|
||||||
|
dst.Spec = BatchReleaseSpec{}
|
||||||
|
srcSpec := srcV1beta1.Spec
|
||||||
|
dst.Spec.TargetRef.WorkloadRef = &WorkloadRef{
|
||||||
|
APIVersion: srcSpec.WorkloadRef.APIVersion,
|
||||||
|
Kind: srcSpec.WorkloadRef.Kind,
|
||||||
|
Name: srcSpec.WorkloadRef.Name,
|
||||||
|
}
|
||||||
|
dst.Spec.ReleasePlan = ReleasePlan{
|
||||||
|
BatchPartition: srcSpec.ReleasePlan.BatchPartition,
|
||||||
|
RolloutID: srcSpec.ReleasePlan.RolloutID,
|
||||||
|
FailureThreshold: srcSpec.ReleasePlan.FailureThreshold,
|
||||||
|
FinalizingPolicy: FinalizingPolicyType(srcSpec.ReleasePlan.FinalizingPolicy),
|
||||||
|
}
|
||||||
|
for _, batch := range srcSpec.ReleasePlan.Batches {
|
||||||
|
obj := ReleaseBatch{
|
||||||
|
CanaryReplicas: batch.CanaryReplicas,
|
||||||
|
}
|
||||||
|
dst.Spec.ReleasePlan.Batches = append(dst.Spec.ReleasePlan.Batches, obj)
|
||||||
|
}
|
||||||
|
if srcSpec.ReleasePlan.PatchPodTemplateMetadata != nil {
|
||||||
|
dst.Spec.ReleasePlan.PatchPodTemplateMetadata = &PatchPodTemplateMetadata{
|
||||||
|
Annotations: map[string]string{},
|
||||||
|
Labels: map[string]string{},
|
||||||
|
}
|
||||||
|
for k, v := range srcSpec.ReleasePlan.PatchPodTemplateMetadata.Annotations {
|
||||||
|
dst.Spec.ReleasePlan.PatchPodTemplateMetadata.Annotations[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range srcSpec.ReleasePlan.PatchPodTemplateMetadata.Labels {
|
||||||
|
dst.Spec.ReleasePlan.PatchPodTemplateMetadata.Labels[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dst.Annotations == nil {
|
||||||
|
dst.Annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
if srcV1beta1.Spec.ReleasePlan.EnableExtraWorkloadForCanary {
|
||||||
|
dst.Annotations[RolloutStyleAnnotation] = string(CanaryRollingStyle)
|
||||||
|
} else {
|
||||||
|
dst.Annotations[RolloutStyleAnnotation] = string(PartitionRollingStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// status
|
||||||
|
dst.Status = BatchReleaseStatus{
|
||||||
|
StableRevision: srcV1beta1.Status.StableRevision,
|
||||||
|
UpdateRevision: srcV1beta1.Status.UpdateRevision,
|
||||||
|
ObservedGeneration: srcV1beta1.Status.ObservedGeneration,
|
||||||
|
ObservedRolloutID: srcV1beta1.Status.ObservedRolloutID,
|
||||||
|
ObservedWorkloadReplicas: srcV1beta1.Status.ObservedWorkloadReplicas,
|
||||||
|
ObservedReleasePlanHash: srcV1beta1.Status.ObservedReleasePlanHash,
|
||||||
|
CollisionCount: srcV1beta1.Status.CollisionCount,
|
||||||
|
Phase: RolloutPhase(srcV1beta1.Status.Phase),
|
||||||
|
}
|
||||||
|
for _, cond := range srcV1beta1.Status.Conditions {
|
||||||
|
obj := RolloutCondition{
|
||||||
|
Type: RolloutConditionType(cond.Type),
|
||||||
|
Status: cond.Status,
|
||||||
|
LastUpdateTime: cond.LastUpdateTime,
|
||||||
|
LastTransitionTime: cond.LastTransitionTime,
|
||||||
|
Reason: cond.Reason,
|
||||||
|
Message: cond.Message,
|
||||||
|
}
|
||||||
|
dst.Status.Conditions = append(dst.Status.Conditions, obj)
|
||||||
|
}
|
||||||
|
dst.Status.CanaryStatus = BatchReleaseCanaryStatus{
|
||||||
|
CurrentBatchState: BatchReleaseBatchStateType(srcV1beta1.Status.CanaryStatus.CurrentBatchState),
|
||||||
|
CurrentBatch: srcV1beta1.Status.CanaryStatus.CurrentBatch,
|
||||||
|
BatchReadyTime: srcV1beta1.Status.CanaryStatus.BatchReadyTime,
|
||||||
|
UpdatedReplicas: srcV1beta1.Status.CanaryStatus.UpdatedReplicas,
|
||||||
|
UpdatedReadyReplicas: srcV1beta1.Status.CanaryStatus.UpdatedReadyReplicas,
|
||||||
|
NoNeedUpdateReplicas: srcV1beta1.Status.CanaryStatus.NoNeedUpdateReplicas,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type %v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Kruise 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 v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,10 @@ type ReleasePlan struct {
|
||||||
// only support for canary deployment
|
// only support for canary deployment
|
||||||
// +optional
|
// +optional
|
||||||
PatchPodTemplateMetadata *PatchPodTemplateMetadata `json:"patchPodTemplateMetadata,omitempty"`
|
PatchPodTemplateMetadata *PatchPodTemplateMetadata `json:"patchPodTemplateMetadata,omitempty"`
|
||||||
|
// If true, then it will create new deployment for canary, such as: workload-demo-canary.
|
||||||
|
// When user verifies that the canary version is ready, we will remove the canary deployment and release the deployment workload-demo in full.
|
||||||
|
// Current only support k8s native deployment
|
||||||
|
EnableExtraWorkloadForCanary bool `json:"enableExtraWorkloadForCanary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FinalizingPolicyType string
|
type FinalizingPolicyType string
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,9 @@ type BatchRelease struct {
|
||||||
|
|
||||||
// BatchReleaseSpec defines how to describe an update between different compRevision
|
// BatchReleaseSpec defines how to describe an update between different compRevision
|
||||||
type BatchReleaseSpec struct {
|
type BatchReleaseSpec struct {
|
||||||
// TargetRef contains the GVK and name of the workload that we need to upgrade to.
|
// WorkloadRef contains enough information to let you identify a workload for Rollout
|
||||||
TargetRef ObjectRef `json:"targetReference"`
|
// Batch release of the bypass
|
||||||
|
WorkloadRef ObjectRef `json:"workloadRef,omitempty"`
|
||||||
// ReleasePlan is the details on how to rollout the resources
|
// ReleasePlan is the details on how to rollout the resources
|
||||||
ReleasePlan ReleasePlan `json:"releasePlan"`
|
ReleasePlan ReleasePlan `json:"releasePlan"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
package v1beta1
|
package v1beta1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/openkruise/rollouts/api/v1alpha1"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
|
@ -42,51 +41,25 @@ const (
|
||||||
// RollbackInBatchAnnotation is set to rollout annotations.
|
// RollbackInBatchAnnotation is set to rollout annotations.
|
||||||
// RollbackInBatchAnnotation allow use disable quick rollback, and will roll back in batch style.
|
// RollbackInBatchAnnotation allow use disable quick rollback, and will roll back in batch style.
|
||||||
RollbackInBatchAnnotation = "rollouts.kruise.io/rollback-in-batch"
|
RollbackInBatchAnnotation = "rollouts.kruise.io/rollback-in-batch"
|
||||||
|
|
||||||
// RolloutStyleAnnotation define the rolling behavior for Deployment.
|
|
||||||
// must be "partition" or "canary":
|
|
||||||
// * "partition" means rolling in batches just like CloneSet, and will NOT create any extra Workload;
|
|
||||||
// * "canary" means rolling in canary way, and will create a canary Workload.
|
|
||||||
// Currently, only Deployment support both "partition" and "canary" rolling styles.
|
|
||||||
// For other workload types, they only support "partition" styles.
|
|
||||||
// Defaults to "canary" to Deployment.
|
|
||||||
// Defaults to "partition" to the others.
|
|
||||||
RolloutStyleAnnotation = "rollouts.kruise.io/rolling-style"
|
|
||||||
|
|
||||||
// TrafficRoutingAnnotation is the TrafficRouting Name, and it is the Rollout's TrafficRouting.
|
|
||||||
// The Rollout release will trigger the TrafficRouting release. For example:
|
|
||||||
// A microservice consists of three applications, and the invocation relationship is as follows: a -> b -> c,
|
|
||||||
// and application(a, b, c)'s gateway is trafficRouting. Any application(a, b or b) release will trigger TrafficRouting release.
|
|
||||||
TrafficRoutingAnnotation = "rollouts.kruise.io/trafficrouting"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RolloutSpec defines the desired state of Rollout
|
// RolloutSpec defines the desired state of Rollout
|
||||||
type RolloutSpec struct {
|
type RolloutSpec struct {
|
||||||
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||||
// Important: Run "make" to regenerate code after modifying this file
|
// Important: Run "make" to regenerate code after modifying this file
|
||||||
// ObjectRef indicates workload
|
// WorkloadRef contains enough information to let you identify a workload for Rollout
|
||||||
ObjectRef ObjectRef `json:"objectRef"`
|
// Batch release of the bypass
|
||||||
|
WorkloadRef ObjectRef `json:"workloadRef"`
|
||||||
// rollout strategy
|
// rollout strategy
|
||||||
Strategy RolloutStrategy `json:"strategy"`
|
Strategy RolloutStrategy `json:"strategy"`
|
||||||
// DeprecatedRolloutID is the deprecated field.
|
|
||||||
// It is recommended that configure RolloutId in workload.annotations[rollouts.kruise.io/rollout-id].
|
|
||||||
// RolloutID should be changed before each workload revision publication.
|
|
||||||
// It is to distinguish consecutive multiple workload publications and rollout progress.
|
|
||||||
DeprecatedRolloutID string `json:"rolloutID,omitempty"`
|
|
||||||
// if a rollout disabled, then the rollout would not watch changes of workload
|
// if a rollout disabled, then the rollout would not watch changes of workload
|
||||||
//+kubebuilder:validation:Optional
|
//+kubebuilder:validation:Optional
|
||||||
//+kubebuilder:default=false
|
//+kubebuilder:default=false
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ObjectRef holds a references to the Kubernetes object
|
||||||
type ObjectRef struct {
|
type ObjectRef struct {
|
||||||
// WorkloadRef contains enough information to let you identify a workload for Rollout
|
|
||||||
// Batch release of the bypass
|
|
||||||
WorkloadRef *WorkloadRef `json:"workloadRef,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// WorkloadRef holds a references to the Kubernetes object
|
|
||||||
type WorkloadRef struct {
|
|
||||||
// API Version of the referent
|
// API Version of the referent
|
||||||
APIVersion string `json:"apiVersion"`
|
APIVersion string `json:"apiVersion"`
|
||||||
// Kind of the referent
|
// Kind of the referent
|
||||||
|
|
@ -111,7 +84,7 @@ type CanaryStrategy struct {
|
||||||
Steps []CanaryStep `json:"steps,omitempty"`
|
Steps []CanaryStep `json:"steps,omitempty"`
|
||||||
// TrafficRoutings hosts all the supported service meshes supported to enable more fine-grained traffic routing
|
// TrafficRoutings hosts all the supported service meshes supported to enable more fine-grained traffic routing
|
||||||
// and current only support one TrafficRouting
|
// and current only support one TrafficRouting
|
||||||
TrafficRoutings []v1alpha1.TrafficRoutingRef `json:"trafficRoutings,omitempty"`
|
TrafficRoutings []TrafficRoutingRef `json:"trafficRoutings,omitempty"`
|
||||||
// FailureThreshold indicates how many failed pods can be tolerated in all upgraded pods.
|
// FailureThreshold indicates how many failed pods can be tolerated in all upgraded pods.
|
||||||
// Only when FailureThreshold are satisfied, Rollout can enter ready state.
|
// Only when FailureThreshold are satisfied, Rollout can enter ready state.
|
||||||
// If FailureThreshold is nil, Rollout will use the MaxUnavailable of workload as its
|
// If FailureThreshold is nil, Rollout will use the MaxUnavailable of workload as its
|
||||||
|
|
@ -122,6 +95,12 @@ type CanaryStrategy struct {
|
||||||
// only support for canary deployment
|
// only support for canary deployment
|
||||||
// +optional
|
// +optional
|
||||||
PatchPodTemplateMetadata *PatchPodTemplateMetadata `json:"patchPodTemplateMetadata,omitempty"`
|
PatchPodTemplateMetadata *PatchPodTemplateMetadata `json:"patchPodTemplateMetadata,omitempty"`
|
||||||
|
// If true, then it will create new deployment for canary, such as: workload-demo-canary.
|
||||||
|
// When user verifies that the canary version is ready, we will remove the canary deployment and release the deployment workload-demo in full.
|
||||||
|
// Current only support k8s native deployment
|
||||||
|
EnableExtraWorkloadForCanary bool `json:"enableExtraWorkloadForCanary,omitempty"`
|
||||||
|
// TrafficRoutingRef is TrafficRouting's Name
|
||||||
|
TrafficRoutingRef string `json:"trafficRoutingRef,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PatchPodTemplateMetadata struct {
|
type PatchPodTemplateMetadata struct {
|
||||||
|
|
@ -133,7 +112,7 @@ type PatchPodTemplateMetadata struct {
|
||||||
|
|
||||||
// CanaryStep defines a step of a canary workload.
|
// CanaryStep defines a step of a canary workload.
|
||||||
type CanaryStep struct {
|
type CanaryStep struct {
|
||||||
v1alpha1.TrafficRoutingStrategy `json:",inline"`
|
TrafficRoutingStrategy `json:",inline"`
|
||||||
// Replicas is the number of expected canary pods in this batch
|
// Replicas is the number of expected canary pods in this batch
|
||||||
// it can be an absolute number (ex: 5) or a percentage of total pods.
|
// it can be an absolute number (ex: 5) or a percentage of total pods.
|
||||||
Replicas *intstr.IntOrString `json:"replicas,omitempty"`
|
Replicas *intstr.IntOrString `json:"replicas,omitempty"`
|
||||||
|
|
@ -142,6 +121,35 @@ type CanaryStep struct {
|
||||||
Pause RolloutPause `json:"pause,omitempty"`
|
Pause RolloutPause `json:"pause,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TrafficRoutingStrategy struct {
|
||||||
|
// Traffic indicate how many percentage of traffic the canary pods should receive
|
||||||
|
// +optional
|
||||||
|
Traffic *string `json:"traffic,omitempty"`
|
||||||
|
// Set overwrites the request with the given header (name, value)
|
||||||
|
// before the action.
|
||||||
|
//
|
||||||
|
// Input:
|
||||||
|
// GET /foo HTTP/1.1
|
||||||
|
// my-header: foo
|
||||||
|
//
|
||||||
|
// requestHeaderModifier:
|
||||||
|
// set:
|
||||||
|
// - name: "my-header"
|
||||||
|
// value: "bar"
|
||||||
|
//
|
||||||
|
// Output:
|
||||||
|
// GET /foo HTTP/1.1
|
||||||
|
// my-header: bar
|
||||||
|
//
|
||||||
|
// +optional
|
||||||
|
RequestHeaderModifier *gatewayv1alpha2.HTTPRequestHeaderFilter `json:"requestHeaderModifier,omitempty"`
|
||||||
|
// Matches define conditions used for matching the incoming HTTP requests to canary service.
|
||||||
|
// Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied.
|
||||||
|
// If Gateway API, current only support one match.
|
||||||
|
// And cannot support both weight and matches, if both are configured, then matches takes precedence.
|
||||||
|
Matches []HttpRouteMatch `json:"matches,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type HttpRouteMatch struct {
|
type HttpRouteMatch struct {
|
||||||
// Headers specifies HTTP request header matchers. Multiple match values are
|
// Headers specifies HTTP request header matchers. Multiple match values are
|
||||||
// ANDed together, meaning, a request must match all the specified headers
|
// ANDed together, meaning, a request must match all the specified headers
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Kruise 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 v1beta1
|
||||||
|
|
||||||
|
// TrafficRoutingRef hosts all the different configuration for supported service meshes to enable more fine-grained traffic routing
|
||||||
|
type TrafficRoutingRef struct {
|
||||||
|
// Service holds the name of a service which selects pods with stable version and don't select any pods with canary version.
|
||||||
|
Service string `json:"service"`
|
||||||
|
// Optional duration in seconds the traffic provider(e.g. nginx ingress controller) consumes the service, ingress configuration changes gracefully.
|
||||||
|
GracePeriodSeconds int32 `json:"gracePeriodSeconds,omitempty"`
|
||||||
|
// Ingress holds Ingress specific configuration to route traffic, e.g. Nginx, Alb.
|
||||||
|
Ingress *IngressTrafficRouting `json:"ingress,omitempty"`
|
||||||
|
// Gateway holds Gateway specific configuration to route traffic
|
||||||
|
// Gateway configuration only supports >= v0.4.0 (v1alpha2).
|
||||||
|
Gateway *GatewayTrafficRouting `json:"gateway,omitempty"`
|
||||||
|
// CustomNetworkRefs hold a list of custom providers to route traffic
|
||||||
|
CustomNetworkRefs []ObjectRef `json:"customNetworkRefs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IngressTrafficRouting configuration for ingress controller to control traffic routing
|
||||||
|
type IngressTrafficRouting struct {
|
||||||
|
// ClassType refers to the type of `Ingress`.
|
||||||
|
// current support nginx, aliyun-alb. default is nginx.
|
||||||
|
// +optional
|
||||||
|
ClassType string `json:"classType,omitempty"`
|
||||||
|
// Name refers to the name of an `Ingress` resource in the same namespace as the `Rollout`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GatewayTrafficRouting configuration for gateway api
|
||||||
|
type GatewayTrafficRouting struct {
|
||||||
|
// HTTPRouteName refers to the name of an `HTTPRoute` resource in the same namespace as the `Rollout`
|
||||||
|
HTTPRouteName *string `json:"httpRouteName,omitempty"`
|
||||||
|
// TCPRouteName *string `json:"tcpRouteName,omitempty"`
|
||||||
|
// UDPRouteName *string `json:"udpRouteName,omitempty"`
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,6 @@ limitations under the License.
|
||||||
package v1beta1
|
package v1beta1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/openkruise/rollouts/api/v1alpha1"
|
|
||||||
"k8s.io/api/apps/v1"
|
"k8s.io/api/apps/v1"
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
|
@ -115,7 +114,7 @@ func (in *BatchReleaseList) DeepCopyObject() runtime.Object {
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *BatchReleaseSpec) DeepCopyInto(out *BatchReleaseSpec) {
|
func (in *BatchReleaseSpec) DeepCopyInto(out *BatchReleaseSpec) {
|
||||||
*out = *in
|
*out = *in
|
||||||
in.TargetRef.DeepCopyInto(&out.TargetRef)
|
out.WorkloadRef = in.WorkloadRef
|
||||||
in.ReleasePlan.DeepCopyInto(&out.ReleasePlan)
|
in.ReleasePlan.DeepCopyInto(&out.ReleasePlan)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,7 +209,7 @@ func (in *CanaryStrategy) DeepCopyInto(out *CanaryStrategy) {
|
||||||
}
|
}
|
||||||
if in.TrafficRoutings != nil {
|
if in.TrafficRoutings != nil {
|
||||||
in, out := &in.TrafficRoutings, &out.TrafficRoutings
|
in, out := &in.TrafficRoutings, &out.TrafficRoutings
|
||||||
*out = make([]v1alpha1.TrafficRoutingRef, len(*in))
|
*out = make([]TrafficRoutingRef, len(*in))
|
||||||
for i := range *in {
|
for i := range *in {
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
}
|
}
|
||||||
|
|
@ -273,6 +272,26 @@ func (in *DeploymentStrategy) DeepCopy() *DeploymentStrategy {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *GatewayTrafficRouting) DeepCopyInto(out *GatewayTrafficRouting) {
|
||||||
|
*out = *in
|
||||||
|
if in.HTTPRouteName != nil {
|
||||||
|
in, out := &in.HTTPRouteName, &out.HTTPRouteName
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayTrafficRouting.
|
||||||
|
func (in *GatewayTrafficRouting) DeepCopy() *GatewayTrafficRouting {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(GatewayTrafficRouting)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *HttpRouteMatch) DeepCopyInto(out *HttpRouteMatch) {
|
func (in *HttpRouteMatch) DeepCopyInto(out *HttpRouteMatch) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
@ -295,14 +314,24 @@ func (in *HttpRouteMatch) DeepCopy() *HttpRouteMatch {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *IngressTrafficRouting) DeepCopyInto(out *IngressTrafficRouting) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressTrafficRouting.
|
||||||
|
func (in *IngressTrafficRouting) DeepCopy() *IngressTrafficRouting {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(IngressTrafficRouting)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *ObjectRef) DeepCopyInto(out *ObjectRef) {
|
func (in *ObjectRef) DeepCopyInto(out *ObjectRef) {
|
||||||
*out = *in
|
*out = *in
|
||||||
if in.WorkloadRef != nil {
|
|
||||||
in, out := &in.WorkloadRef, &out.WorkloadRef
|
|
||||||
*out = new(WorkloadRef)
|
|
||||||
**out = **in
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectRef.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectRef.
|
||||||
|
|
@ -494,7 +523,7 @@ func (in *RolloutPause) DeepCopy() *RolloutPause {
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *RolloutSpec) DeepCopyInto(out *RolloutSpec) {
|
func (in *RolloutSpec) DeepCopyInto(out *RolloutSpec) {
|
||||||
*out = *in
|
*out = *in
|
||||||
in.ObjectRef.DeepCopyInto(&out.ObjectRef)
|
out.WorkloadRef = in.WorkloadRef
|
||||||
in.Strategy.DeepCopyInto(&out.Strategy)
|
in.Strategy.DeepCopyInto(&out.Strategy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -556,16 +585,63 @@ func (in *RolloutStrategy) DeepCopy() *RolloutStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *WorkloadRef) DeepCopyInto(out *WorkloadRef) {
|
func (in *TrafficRoutingRef) DeepCopyInto(out *TrafficRoutingRef) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.Ingress != nil {
|
||||||
|
in, out := &in.Ingress, &out.Ingress
|
||||||
|
*out = new(IngressTrafficRouting)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.Gateway != nil {
|
||||||
|
in, out := &in.Gateway, &out.Gateway
|
||||||
|
*out = new(GatewayTrafficRouting)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.CustomNetworkRefs != nil {
|
||||||
|
in, out := &in.CustomNetworkRefs, &out.CustomNetworkRefs
|
||||||
|
*out = make([]ObjectRef, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadRef.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRoutingRef.
|
||||||
func (in *WorkloadRef) DeepCopy() *WorkloadRef {
|
func (in *TrafficRoutingRef) DeepCopy() *TrafficRoutingRef {
|
||||||
if in == nil {
|
if in == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
out := new(WorkloadRef)
|
out := new(TrafficRoutingRef)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TrafficRoutingStrategy) DeepCopyInto(out *TrafficRoutingStrategy) {
|
||||||
|
*out = *in
|
||||||
|
if in.Traffic != nil {
|
||||||
|
in, out := &in.Traffic, &out.Traffic
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.RequestHeaderModifier != nil {
|
||||||
|
in, out := &in.RequestHeaderModifier, &out.RequestHeaderModifier
|
||||||
|
*out = new(v1alpha2.HTTPRequestHeaderFilter)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.Matches != nil {
|
||||||
|
in, out := &in.Matches, &out.Matches
|
||||||
|
*out = make([]HttpRouteMatch, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRoutingStrategy.
|
||||||
|
func (in *TrafficRoutingStrategy) DeepCopy() *TrafficRoutingStrategy {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TrafficRoutingStrategy)
|
||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,13 @@ spec:
|
||||||
- canaryReplicas
|
- canaryReplicas
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
enableExtraWorkloadForCanary:
|
||||||
|
description: 'If true, then it will create new deployment for
|
||||||
|
canary, such as: workload-demo-canary. When user verifies that
|
||||||
|
the canary version is ready, we will remove the canary deployment
|
||||||
|
and release the deployment workload-demo in full. Current only
|
||||||
|
support k8s native deployment'
|
||||||
|
type: boolean
|
||||||
failureThreshold:
|
failureThreshold:
|
||||||
anyOf:
|
anyOf:
|
||||||
- type: integer
|
- type: integer
|
||||||
|
|
@ -378,33 +385,29 @@ spec:
|
||||||
rolloutID:
|
rolloutID:
|
||||||
description: RolloutID indicates an id for each rollout progress
|
description: RolloutID indicates an id for each rollout progress
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- enableExtraWorkloadForCanary
|
||||||
type: object
|
type: object
|
||||||
targetReference:
|
workloadRef:
|
||||||
description: TargetRef contains the GVK and name of the workload that
|
description: WorkloadRef contains enough information to let you identify
|
||||||
we need to upgrade to.
|
a workload for Rollout Batch release of the bypass
|
||||||
properties:
|
properties:
|
||||||
workloadRef:
|
apiVersion:
|
||||||
description: WorkloadRef contains enough information to let you
|
description: API Version of the referent
|
||||||
identify a workload for Rollout Batch release of the bypass
|
type: string
|
||||||
properties:
|
kind:
|
||||||
apiVersion:
|
description: Kind of the referent
|
||||||
description: API Version of the referent
|
type: string
|
||||||
type: string
|
name:
|
||||||
kind:
|
description: Name of the referent
|
||||||
description: Kind of the referent
|
type: string
|
||||||
type: string
|
required:
|
||||||
name:
|
- apiVersion
|
||||||
description: Name of the referent
|
- kind
|
||||||
type: string
|
- name
|
||||||
required:
|
|
||||||
- apiVersion
|
|
||||||
- kind
|
|
||||||
- name
|
|
||||||
type: object
|
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- releasePlan
|
- releasePlan
|
||||||
- targetReference
|
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: BatchReleaseStatus defines the observed state of a release
|
description: BatchReleaseStatus defines the observed state of a release
|
||||||
|
|
|
||||||
|
|
@ -567,37 +567,6 @@ spec:
|
||||||
description: if a rollout disabled, then the rollout would not watch
|
description: if a rollout disabled, then the rollout would not watch
|
||||||
changes of workload
|
changes of workload
|
||||||
type: boolean
|
type: boolean
|
||||||
objectRef:
|
|
||||||
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
|
||||||
Important: Run "make" to regenerate code after modifying this file
|
|
||||||
ObjectRef indicates workload'
|
|
||||||
properties:
|
|
||||||
workloadRef:
|
|
||||||
description: WorkloadRef contains enough information to let you
|
|
||||||
identify a workload for Rollout Batch release of the bypass
|
|
||||||
properties:
|
|
||||||
apiVersion:
|
|
||||||
description: API Version of the referent
|
|
||||||
type: string
|
|
||||||
kind:
|
|
||||||
description: Kind of the referent
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
description: Name of the referent
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- apiVersion
|
|
||||||
- kind
|
|
||||||
- name
|
|
||||||
type: object
|
|
||||||
type: object
|
|
||||||
rolloutID:
|
|
||||||
description: DeprecatedRolloutID is the deprecated field. It is recommended
|
|
||||||
that configure RolloutId in workload.annotations[rollouts.kruise.io/rollout-id].
|
|
||||||
RolloutID should be changed before each workload revision publication.
|
|
||||||
It is to distinguish consecutive multiple workload publications
|
|
||||||
and rollout progress.
|
|
||||||
type: string
|
|
||||||
strategy:
|
strategy:
|
||||||
description: rollout strategy
|
description: rollout strategy
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -605,6 +574,13 @@ spec:
|
||||||
description: CanaryStrategy defines parameters for a Replica Based
|
description: CanaryStrategy defines parameters for a Replica Based
|
||||||
Canary
|
Canary
|
||||||
properties:
|
properties:
|
||||||
|
enableExtraWorkloadForCanary:
|
||||||
|
description: 'If true, then it will create new deployment
|
||||||
|
for canary, such as: workload-demo-canary. When user verifies
|
||||||
|
that the canary version is ready, we will remove the canary
|
||||||
|
deployment and release the deployment workload-demo in full.
|
||||||
|
Current only support k8s native deployment'
|
||||||
|
type: boolean
|
||||||
failureThreshold:
|
failureThreshold:
|
||||||
anyOf:
|
anyOf:
|
||||||
- type: integer
|
- type: integer
|
||||||
|
|
@ -830,13 +806,15 @@ spec:
|
||||||
- name
|
- name
|
||||||
x-kubernetes-list-type: map
|
x-kubernetes-list-type: map
|
||||||
type: object
|
type: object
|
||||||
weight:
|
traffic:
|
||||||
description: Weight indicate how many percentage of
|
description: Traffic indicate how many percentage of
|
||||||
traffic the canary pods should receive
|
traffic the canary pods should receive
|
||||||
format: int32
|
type: string
|
||||||
type: integer
|
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
trafficRoutingRef:
|
||||||
|
description: TrafficRoutingRef is TrafficRouting's Name
|
||||||
|
type: string
|
||||||
trafficRoutings:
|
trafficRoutings:
|
||||||
description: TrafficRoutings hosts all the supported service
|
description: TrafficRoutings hosts all the supported service
|
||||||
meshes supported to enable more fine-grained traffic routing
|
meshes supported to enable more fine-grained traffic routing
|
||||||
|
|
@ -850,12 +828,17 @@ spec:
|
||||||
description: CustomNetworkRefs hold a list of custom
|
description: CustomNetworkRefs hold a list of custom
|
||||||
providers to route traffic
|
providers to route traffic
|
||||||
items:
|
items:
|
||||||
|
description: ObjectRef holds a references to the Kubernetes
|
||||||
|
object
|
||||||
properties:
|
properties:
|
||||||
apiVersion:
|
apiVersion:
|
||||||
|
description: API Version of the referent
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
|
description: Kind of the referent
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
|
description: Name of the referent
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- apiVersion
|
- apiVersion
|
||||||
|
|
@ -911,9 +894,29 @@ spec:
|
||||||
value is false
|
value is false
|
||||||
type: boolean
|
type: boolean
|
||||||
type: object
|
type: object
|
||||||
|
workloadRef:
|
||||||
|
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||||
|
Important: Run "make" to regenerate code after modifying this file
|
||||||
|
WorkloadRef contains enough information to let you identify a workload
|
||||||
|
for Rollout Batch release of the bypass'
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
description: API Version of the referent
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
description: Kind of the referent
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: Name of the referent
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- apiVersion
|
||||||
|
- kind
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
required:
|
required:
|
||||||
- objectRef
|
|
||||||
- strategy
|
- strategy
|
||||||
|
- workloadRef
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: RolloutStatus defines the observed state of Rollout
|
description: RolloutStatus defines the observed state of Rollout
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ resources:
|
||||||
patchesStrategicMerge:
|
patchesStrategicMerge:
|
||||||
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
|
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
|
||||||
# patches here are for enabling the conversion webhook for each CRD
|
# patches here are for enabling the conversion webhook for each CRD
|
||||||
#- patches/webhook_in_rollouts.yaml
|
- patches/webhook_in_rollouts.yaml
|
||||||
#- patches/webhook_in_batchreleases.yaml
|
- patches/webhook_in_batchreleases.yaml
|
||||||
#+kubebuilder:scaffold:crdkustomizewebhookpatch
|
#+kubebuilder:scaffold:crdkustomizewebhookpatch
|
||||||
|
|
||||||
# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
|
# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,4 @@ spec:
|
||||||
name: webhook-service
|
name: webhook-service
|
||||||
path: /convert
|
path: /convert
|
||||||
conversionReviewVersions:
|
conversionReviewVersions:
|
||||||
- v1
|
- v1beta1
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,4 @@ spec:
|
||||||
name: webhook-service
|
name: webhook-service
|
||||||
path: /convert
|
path: /convert
|
||||||
conversionReviewVersions:
|
conversionReviewVersions:
|
||||||
- v1
|
- v1beta1
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
---
|
||||||
|
title: v1beta1-apis-proposal
|
||||||
|
|
||||||
|
authors:
|
||||||
|
- "@zmberg"
|
||||||
|
|
||||||
|
creation-date: 2023-11-07
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
The Kruise Rollout project has been stable for a year, recently we plan to upgrade the apis from v1alpha1 to v1beta1 and optimize some of the fields in response to past questions and community feedback,
|
||||||
|
this proposal will organize the v1beta1 apis and discuss it with the community.
|
||||||
|
|
||||||
|
## Proposal
|
||||||
|
To make it easier to understand, I'm going to introduce the v1beta1 field from 6 scenario.
|
||||||
|
|
||||||
|
### Canary Release
|
||||||
|
```
|
||||||
|
apiVersion: rollouts.kruise.io/v1beta1
|
||||||
|
kind: Rollout
|
||||||
|
metadata:
|
||||||
|
name: rollouts-demo
|
||||||
|
spec:
|
||||||
|
workloadRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: workload-demo
|
||||||
|
strategy:
|
||||||
|
canary:
|
||||||
|
# If true, then it will create new deployment for canary, such as: workload-demo-canary.
|
||||||
|
# When user verifies that the canary version is OK, we will remove the canary deployment and release the deployment workload-demo in full.
|
||||||
|
# Current only support k8s native deployment
|
||||||
|
enableExtraWorkloadForCanary: true
|
||||||
|
steps:
|
||||||
|
- trafficWeight: 20%
|
||||||
|
desiredReplicas: 2
|
||||||
|
trafficRoutings:
|
||||||
|
- service: service-demo
|
||||||
|
ingress:
|
||||||
|
classType: nginx
|
||||||
|
name: ingress-demo
|
||||||
|
```
|
||||||
|
|
||||||
|
### A/B Testing Release
|
||||||
|
```
|
||||||
|
apiVersion: rollouts.kruise.io/v1beta1
|
||||||
|
kind: Rollout
|
||||||
|
metadata:
|
||||||
|
name: rollouts-demo
|
||||||
|
spec:
|
||||||
|
workloadRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: workload-demo
|
||||||
|
strategy:
|
||||||
|
canary:
|
||||||
|
enableExtraWorkloadForCanary: true
|
||||||
|
steps:
|
||||||
|
- desiredReplicas: 2
|
||||||
|
trafficMatches:
|
||||||
|
- headers:
|
||||||
|
- name: user-agent
|
||||||
|
type: Exact
|
||||||
|
value: pc
|
||||||
|
trafficRoutings:
|
||||||
|
- service: service-demo
|
||||||
|
ingress:
|
||||||
|
classType: nginx
|
||||||
|
name: ingress-demo
|
||||||
|
```
|
||||||
|
|
||||||
|
### Only Batch Release
|
||||||
|
```
|
||||||
|
apiVersion: rollouts.kruise.io/v1beta1
|
||||||
|
kind: Rollout
|
||||||
|
metadata:
|
||||||
|
name: rollouts-demo
|
||||||
|
spec:
|
||||||
|
workloadRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: workload-demo
|
||||||
|
strategy:
|
||||||
|
canary:
|
||||||
|
steps:
|
||||||
|
- desiredReplicas: 1
|
||||||
|
- desiredReplicas: 10%
|
||||||
|
# After desiredReplicas Pods are ready, sleep 60 and continue to release later batches.
|
||||||
|
# If you don't configure it, manual confirmation is required by default.
|
||||||
|
pause: {duration: 60}
|
||||||
|
- desiredReplicas: 30%
|
||||||
|
pause: {duration: 60}
|
||||||
|
- desiredReplicas: 60%
|
||||||
|
pause: {duration: 60}
|
||||||
|
- desiredReplicas: 100%
|
||||||
|
pause: {duration: 60}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Release + Traffic Weight
|
||||||
|
```
|
||||||
|
apiVersion: rollouts.kruise.io/v1beta1
|
||||||
|
kind: Rollout
|
||||||
|
metadata:
|
||||||
|
name: rollouts-demo
|
||||||
|
spec:
|
||||||
|
workloadRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: workload-demo
|
||||||
|
strategy:
|
||||||
|
canary:
|
||||||
|
steps:
|
||||||
|
- trafficWeight: 5%
|
||||||
|
desiredReplicas: 2
|
||||||
|
- desiredReplicas: 30%
|
||||||
|
- desiredReplicas: 60%
|
||||||
|
- desiredReplicas: 100%
|
||||||
|
trafficRoutings:
|
||||||
|
- service: service-demo
|
||||||
|
ingress:
|
||||||
|
classType: nginx
|
||||||
|
name: ingress-demo
|
||||||
|
```
|
||||||
|
### Batch Release + Traffic A/B Testing
|
||||||
|
```
|
||||||
|
apiVersion: rollouts.kruise.io/v1beta1
|
||||||
|
kind: Rollout
|
||||||
|
metadata:
|
||||||
|
name: rollouts-demo
|
||||||
|
spec:
|
||||||
|
workloadRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: workload-demo
|
||||||
|
strategy:
|
||||||
|
canary:
|
||||||
|
steps:
|
||||||
|
- trafficMatches:
|
||||||
|
- headers:
|
||||||
|
- name: user-agent
|
||||||
|
type: Exact
|
||||||
|
value: pc
|
||||||
|
desiredReplicas: 2
|
||||||
|
- desiredReplicas: 30%
|
||||||
|
- desiredReplicas: 60%
|
||||||
|
- desiredReplicas: 100%
|
||||||
|
trafficRoutings:
|
||||||
|
- service: service-demo
|
||||||
|
ingress:
|
||||||
|
classType: nginx
|
||||||
|
name: ingress-demo
|
||||||
|
```
|
||||||
|
### End-to-End progressive delivery for microservice application
|
||||||
|
```
|
||||||
|
apiVersion: rollouts.kruise.io/v1alpha1
|
||||||
|
kind: TrafficRouting
|
||||||
|
metadata:
|
||||||
|
name: mse-traffic
|
||||||
|
spec:
|
||||||
|
objectRef:
|
||||||
|
- service: spring-cloud-a
|
||||||
|
ingress:
|
||||||
|
classType: mse
|
||||||
|
name: spring-cloud-a
|
||||||
|
strategy:
|
||||||
|
matches:
|
||||||
|
- headers:
|
||||||
|
- type: Exact
|
||||||
|
name: User-Agent
|
||||||
|
value: xiaoming
|
||||||
|
# http request via ingress, and add header[x-mse-tag]=gray
|
||||||
|
# for mse or istio routing the gray traffic to gray application
|
||||||
|
requestHeaderModifier:
|
||||||
|
set:
|
||||||
|
- name: x-mse-tag
|
||||||
|
value: gray
|
||||||
|
---
|
||||||
|
apiVersion: rollouts.kruise.io/v1alpha1
|
||||||
|
kind: Rollout
|
||||||
|
metadata:
|
||||||
|
name: rollout-a
|
||||||
|
spec:
|
||||||
|
workloadRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: spring-cloud-a
|
||||||
|
strategy:
|
||||||
|
canary:
|
||||||
|
enableExtraWorkloadForCanary: true
|
||||||
|
# Type TrafficRouting's name
|
||||||
|
trafficRoutingRef: mse-traffic
|
||||||
|
steps:
|
||||||
|
- desiredReplicas: 1
|
||||||
|
# patch pod template metadata to canary workload
|
||||||
|
# current only support deployment, and when enableExtraWorkloadForCanary=true
|
||||||
|
patchPodTemplateMetadata:
|
||||||
|
labels:
|
||||||
|
alicloud.service.tag: gray
|
||||||
|
opensergo.io/canary-gray: gray
|
||||||
|
---
|
||||||
|
apiVersion: rollouts.kruise.io/v1alpha1
|
||||||
|
kind: Rollout
|
||||||
|
metadata:
|
||||||
|
name: rollout-a
|
||||||
|
spec:
|
||||||
|
workloadRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: spring-cloud-a
|
||||||
|
strategy:
|
||||||
|
canary:
|
||||||
|
enableExtraWorkloadForCanary: true
|
||||||
|
# Type TrafficRouting's name
|
||||||
|
trafficRoutingRef: mse-traffic
|
||||||
|
steps:
|
||||||
|
- desiredReplicas: 1
|
||||||
|
# patch pod template metadata to canary workload
|
||||||
|
patchPodTemplateMetadata:
|
||||||
|
labels:
|
||||||
|
alicloud.service.tag: gray
|
||||||
|
opensergo.io/canary-gray: gray
|
||||||
|
```
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
utilpointer "k8s.io/utils/pointer"
|
utilpointer "k8s.io/utils/pointer"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
@ -82,8 +83,12 @@ func objectToTable(path string) error {
|
||||||
if rollout != nil {
|
if rollout != nil {
|
||||||
steps := rollout.Spec.Strategy.Canary.Steps
|
steps := rollout.Spec.Strategy.Canary.Steps
|
||||||
for i, step := range steps {
|
for i, step := range steps {
|
||||||
weight := step.TrafficRoutingStrategy.Weight
|
var weight *int32
|
||||||
if step.TrafficRoutingStrategy.Weight == nil {
|
if step.TrafficRoutingStrategy.Traffic != nil {
|
||||||
|
is := intstr.FromString(*step.TrafficRoutingStrategy.Traffic)
|
||||||
|
weightInt, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
|
||||||
|
weight = utilpointer.Int32(int32(weightInt))
|
||||||
|
} else {
|
||||||
weight = utilpointer.Int32(-1)
|
weight = utilpointer.Int32(-1)
|
||||||
}
|
}
|
||||||
var canaryService string
|
var canaryService string
|
||||||
|
|
@ -111,13 +116,19 @@ func objectToTable(path string) error {
|
||||||
var canaryService string
|
var canaryService string
|
||||||
stableService := trafficRouting.Spec.ObjectRef[0].Service
|
stableService := trafficRouting.Spec.ObjectRef[0].Service
|
||||||
canaryService = stableService
|
canaryService = stableService
|
||||||
|
matches := make([]v1beta1.HttpRouteMatch, 0)
|
||||||
|
for _, match := range trafficRouting.Spec.Strategy.Matches {
|
||||||
|
obj := v1beta1.HttpRouteMatch{}
|
||||||
|
obj.Headers = match.Headers
|
||||||
|
matches = append(matches, obj)
|
||||||
|
}
|
||||||
data := &custom.LuaData{
|
data := &custom.LuaData{
|
||||||
Data: custom.Data{
|
Data: custom.Data{
|
||||||
Labels: testCase.Original.GetLabels(),
|
Labels: testCase.Original.GetLabels(),
|
||||||
Annotations: testCase.Original.GetAnnotations(),
|
Annotations: testCase.Original.GetAnnotations(),
|
||||||
Spec: testCase.Original.Object["spec"],
|
Spec: testCase.Original.Object["spec"],
|
||||||
},
|
},
|
||||||
Matches: trafficRouting.Spec.Strategy.Matches,
|
Matches: matches,
|
||||||
CanaryWeight: *weight,
|
CanaryWeight: *weight,
|
||||||
StableWeight: 100 - *weight,
|
StableWeight: 100 - *weight,
|
||||||
CanaryService: canaryService,
|
CanaryService: canaryService,
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,13 @@
|
||||||
rollout:
|
rollout:
|
||||||
apiVersion: rollouts.kruise.io/v1alpha1
|
apiVersion: rollouts.kruise.io/v1beta1
|
||||||
kind: Rollout
|
kind: Rollout
|
||||||
metadata:
|
metadata:
|
||||||
name: rollouts-demo
|
name: rollouts-demo
|
||||||
annotations:
|
|
||||||
rollouts.kruise.io/rolling-style: canary
|
|
||||||
spec:
|
spec:
|
||||||
disabled: false
|
workloadRef:
|
||||||
objectRef:
|
apiVersion: apps/v1
|
||||||
workloadRef:
|
kind: Deployment
|
||||||
apiVersion: apps/v1
|
name: deploy-demo
|
||||||
kind: Deployment
|
|
||||||
name: deploy-demo
|
|
||||||
strategy:
|
strategy:
|
||||||
canary:
|
canary:
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -32,7 +28,7 @@ rollout:
|
||||||
- type: RegularExpression
|
- type: RegularExpression
|
||||||
name: name
|
name: name
|
||||||
value: ".*demo"
|
value: ".*demo"
|
||||||
- weight: 50
|
- traffic: "50%"
|
||||||
trafficRoutings:
|
trafficRoutings:
|
||||||
- service: svc-demo
|
- service: svc-demo
|
||||||
customNetworkRefs:
|
customNetworkRefs:
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ function GenerateRoutes(spec, stableService, canaryService, stableWeight, canary
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if (obj.matches)
|
if (obj.matches and next(obj.matches) ~= nil)
|
||||||
then
|
then
|
||||||
GenerateRoutesWithMatches(spec, obj.matches, obj.stableService, obj.canaryService)
|
GenerateRoutesWithMatches(spec, obj.matches, obj.stableService, obj.canaryService)
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -167,10 +167,10 @@ func (r *BatchReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Request
|
||||||
klog.Infof("Begin to reconcile BatchRelease(%v/%v), release-phase: %v", release.Namespace, release.Name, release.Status.Phase)
|
klog.Infof("Begin to reconcile BatchRelease(%v/%v), release-phase: %v", release.Namespace, release.Name, release.Status.Phase)
|
||||||
|
|
||||||
// If workload watcher does not exist, then add the watcher dynamically
|
// If workload watcher does not exist, then add the watcher dynamically
|
||||||
workloadRef := release.Spec.TargetRef.WorkloadRef
|
workloadRef := release.Spec.WorkloadRef
|
||||||
workloadGVK := util.GetGVKFrom(workloadRef)
|
workloadGVK := util.GetGVKFrom(&workloadRef)
|
||||||
_, exists := watchedWorkload.Load(workloadGVK.String())
|
_, exists := watchedWorkload.Load(workloadGVK.String())
|
||||||
if workloadRef != nil && !exists {
|
if !exists {
|
||||||
succeeded, err := util.AddWatcherDynamically(runtimeController, workloadHandler, workloadGVK)
|
succeeded, err := util.AddWatcherDynamically(runtimeController, workloadHandler, workloadGVK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
|
|
|
||||||
|
|
@ -61,15 +61,14 @@ var (
|
||||||
UID: types.UID("87076677"),
|
UID: types.UID("87076677"),
|
||||||
},
|
},
|
||||||
Spec: v1beta1.BatchReleaseSpec{
|
Spec: v1beta1.BatchReleaseSpec{
|
||||||
TargetRef: v1beta1.ObjectRef{
|
WorkloadRef: v1beta1.ObjectRef{
|
||||||
WorkloadRef: &v1beta1.WorkloadRef{
|
APIVersion: "apps/v1",
|
||||||
APIVersion: "apps/v1",
|
Kind: "Deployment",
|
||||||
Kind: "Deployment",
|
Name: "sample",
|
||||||
Name: "sample",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
ReleasePlan: v1beta1.ReleasePlan{
|
ReleasePlan: v1beta1.ReleasePlan{
|
||||||
BatchPartition: pointer.Int32(0),
|
EnableExtraWorkloadForCanary: true,
|
||||||
|
BatchPartition: pointer.Int32(0),
|
||||||
Batches: []v1beta1.ReleaseBatch{
|
Batches: []v1beta1.ReleaseBatch{
|
||||||
{
|
{
|
||||||
CanaryReplicas: intstr.FromString("10%"),
|
CanaryReplicas: intstr.FromString("10%"),
|
||||||
|
|
@ -141,12 +140,10 @@ var (
|
||||||
UID: types.UID("87076677"),
|
UID: types.UID("87076677"),
|
||||||
},
|
},
|
||||||
Spec: v1beta1.BatchReleaseSpec{
|
Spec: v1beta1.BatchReleaseSpec{
|
||||||
TargetRef: v1beta1.ObjectRef{
|
WorkloadRef: v1beta1.ObjectRef{
|
||||||
WorkloadRef: &v1beta1.WorkloadRef{
|
APIVersion: "apps.kruise.io/v1alpha1",
|
||||||
APIVersion: "apps.kruise.io/v1alpha1",
|
Kind: "CloneSet",
|
||||||
Kind: "CloneSet",
|
Name: "sample",
|
||||||
Name: "sample",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
ReleasePlan: v1beta1.ReleasePlan{
|
ReleasePlan: v1beta1.ReleasePlan{
|
||||||
BatchPartition: pointer.Int32Ptr(0),
|
BatchPartition: pointer.Int32Ptr(0),
|
||||||
|
|
|
||||||
|
|
@ -240,14 +240,14 @@ func getBatchRelease(c client.Reader, workloadNamespaceName types.NamespacedName
|
||||||
|
|
||||||
for i := range brList.Items {
|
for i := range brList.Items {
|
||||||
br := &brList.Items[i]
|
br := &brList.Items[i]
|
||||||
targetRef := br.Spec.TargetRef
|
targetRef := br.Spec.WorkloadRef
|
||||||
targetGV, err := schema.ParseGroupVersion(targetRef.WorkloadRef.APIVersion)
|
targetGV, err := schema.ParseGroupVersion(targetRef.APIVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Failed to parse targetRef's group version: %s for BatchRelease(%v)", targetRef.WorkloadRef.APIVersion, client.ObjectKeyFromObject(br))
|
klog.Errorf("Failed to parse targetRef's group version: %s for BatchRelease(%v)", targetRef.APIVersion, client.ObjectKeyFromObject(br))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if targetRef.WorkloadRef.Kind == gvk.Kind && targetGV.Group == gvk.Group && targetRef.WorkloadRef.Name == workloadNamespaceName.Name {
|
if targetRef.Kind == gvk.Kind && targetGV.Group == gvk.Group && targetRef.Name == workloadNamespaceName.Name {
|
||||||
nsn = client.ObjectKeyFromObject(br)
|
nsn = client.ObjectKeyFromObject(br)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,9 @@ package batchrelease
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
appsv1alpha1 "github.com/openkruise/kruise-api/apps/v1alpha1"
|
appsv1alpha1 "github.com/openkruise/kruise-api/apps/v1alpha1"
|
||||||
"github.com/openkruise/rollouts/api/v1alpha1"
|
|
||||||
"github.com/openkruise/rollouts/api/v1beta1"
|
"github.com/openkruise/rollouts/api/v1beta1"
|
||||||
"github.com/openkruise/rollouts/pkg/controller/batchrelease/control"
|
"github.com/openkruise/rollouts/pkg/controller/batchrelease/control"
|
||||||
"github.com/openkruise/rollouts/pkg/controller/batchrelease/control/canarystyle"
|
"github.com/openkruise/rollouts/pkg/controller/batchrelease/control/canarystyle"
|
||||||
|
|
@ -187,11 +185,7 @@ func (r *Executor) progressBatches(release *v1beta1.BatchRelease, newStatus *v1b
|
||||||
|
|
||||||
// GetWorkloadController pick the right workload controller to work on the workload
|
// GetWorkloadController pick the right workload controller to work on the workload
|
||||||
func (r *Executor) getReleaseController(release *v1beta1.BatchRelease, newStatus *v1beta1.BatchReleaseStatus) (control.Interface, error) {
|
func (r *Executor) getReleaseController(release *v1beta1.BatchRelease, newStatus *v1beta1.BatchReleaseStatus) (control.Interface, error) {
|
||||||
targetRef := release.Spec.TargetRef.WorkloadRef
|
targetRef := release.Spec.WorkloadRef
|
||||||
if targetRef == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
gvk := schema.FromAPIVersionAndKind(targetRef.APIVersion, targetRef.Kind)
|
gvk := schema.FromAPIVersionAndKind(targetRef.APIVersion, targetRef.Kind)
|
||||||
if !util.IsSupportedWorkload(gvk) {
|
if !util.IsSupportedWorkload(gvk) {
|
||||||
message := fmt.Sprintf("the workload type '%v' is not supported", gvk)
|
message := fmt.Sprintf("the workload type '%v' is not supported", gvk)
|
||||||
|
|
@ -217,7 +211,7 @@ func (r *Executor) getReleaseController(release *v1beta1.BatchRelease, newStatus
|
||||||
|
|
||||||
case apps.SchemeGroupVersion.String():
|
case apps.SchemeGroupVersion.String():
|
||||||
if targetRef.Kind == reflect.TypeOf(apps.Deployment{}).Name() {
|
if targetRef.Kind == reflect.TypeOf(apps.Deployment{}).Name() {
|
||||||
if strings.EqualFold(release.Annotations[v1beta1.RolloutStyleAnnotation], string(v1alpha1.PartitionRollingStyle)) {
|
if !release.Spec.ReleasePlan.EnableExtraWorkloadForCanary {
|
||||||
klog.InfoS("Using Deployment partition-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
|
klog.InfoS("Using Deployment partition-style release controller for this batch release", "workload name", targetKey.Name, "namespace", targetKey.Namespace)
|
||||||
return partitionstyle.NewControlPlane(partitiondeployment.NewController, r.client, r.recorder, release, newStatus, targetKey, gvk), nil
|
return partitionstyle.NewControlPlane(partitiondeployment.NewController, r.client, r.recorder, release, newStatus, targetKey, gvk), nil
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -131,12 +131,10 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TargetRef: v1beta1.ObjectRef{
|
WorkloadRef: v1beta1.ObjectRef{
|
||||||
WorkloadRef: &v1beta1.WorkloadRef{
|
APIVersion: deploymentDemo.APIVersion,
|
||||||
APIVersion: deploymentDemo.APIVersion,
|
Kind: deploymentDemo.Kind,
|
||||||
Kind: deploymentDemo.Kind,
|
Name: deploymentDemo.Name,
|
||||||
Name: deploymentDemo.Name,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Status: v1beta1.BatchReleaseStatus{
|
Status: v1beta1.BatchReleaseStatus{
|
||||||
|
|
|
||||||
|
|
@ -129,12 +129,10 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TargetRef: v1beta1.ObjectRef{
|
WorkloadRef: v1beta1.ObjectRef{
|
||||||
WorkloadRef: &v1beta1.WorkloadRef{
|
APIVersion: cloneDemo.APIVersion,
|
||||||
APIVersion: cloneDemo.APIVersion,
|
Kind: cloneDemo.Kind,
|
||||||
Kind: cloneDemo.Kind,
|
Name: cloneDemo.Name,
|
||||||
Name: cloneDemo.Name,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Status: v1beta1.BatchReleaseStatus{
|
Status: v1beta1.BatchReleaseStatus{
|
||||||
|
|
|
||||||
|
|
@ -117,12 +117,10 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TargetRef: v1beta1.ObjectRef{
|
WorkloadRef: v1beta1.ObjectRef{
|
||||||
WorkloadRef: &v1beta1.WorkloadRef{
|
APIVersion: daemonDemo.APIVersion,
|
||||||
APIVersion: daemonDemo.APIVersion,
|
Kind: daemonDemo.Kind,
|
||||||
Kind: daemonDemo.Kind,
|
Name: daemonDemo.Name,
|
||||||
Name: daemonDemo.Name,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Status: v1beta1.BatchReleaseStatus{
|
Status: v1beta1.BatchReleaseStatus{
|
||||||
|
|
|
||||||
|
|
@ -131,12 +131,10 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TargetRef: v1beta1.ObjectRef{
|
WorkloadRef: v1beta1.ObjectRef{
|
||||||
WorkloadRef: &v1beta1.WorkloadRef{
|
APIVersion: deploymentDemo.APIVersion,
|
||||||
APIVersion: deploymentDemo.APIVersion,
|
Kind: deploymentDemo.Kind,
|
||||||
Kind: deploymentDemo.Kind,
|
Name: deploymentDemo.Name,
|
||||||
Name: deploymentDemo.Name,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Status: v1beta1.BatchReleaseStatus{
|
Status: v1beta1.BatchReleaseStatus{
|
||||||
|
|
|
||||||
|
|
@ -136,12 +136,10 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TargetRef: v1beta1.ObjectRef{
|
WorkloadRef: v1beta1.ObjectRef{
|
||||||
WorkloadRef: &v1beta1.WorkloadRef{
|
APIVersion: stsDemo.APIVersion,
|
||||||
APIVersion: stsDemo.APIVersion,
|
Kind: stsDemo.Kind,
|
||||||
Kind: stsDemo.Kind,
|
Name: stsDemo.Name,
|
||||||
Name: stsDemo.Name,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Status: v1beta1.BatchReleaseStatus{
|
Status: v1beta1.BatchReleaseStatus{
|
||||||
|
|
|
||||||
|
|
@ -159,8 +159,8 @@ func TestSyncDeployment(t *testing.T) {
|
||||||
rs.Status.Replicas = replicas
|
rs.Status.Replicas = replicas
|
||||||
if strings.HasPrefix(name, "scale") {
|
if strings.HasPrefix(name, "scale") {
|
||||||
rs.Annotations = map[string]string{
|
rs.Annotations = map[string]string{
|
||||||
util.DesiredReplicasAnnotation: strconv.Itoa(-1),
|
util.ReplicasAnnotation: strconv.Itoa(-1),
|
||||||
util.MaxReplicasAnnotation: strconv.Itoa(int(test.dAvailable + test.maxSurge.IntVal)),
|
util.MaxReplicasAnnotation: strconv.Itoa(int(test.dAvailable + test.maxSurge.IntVal)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rs.Spec.Template.Spec.Containers[0].Image = fmt.Sprintf("old-version-%d", index)
|
rs.Spec.Template.Spec.Containers[0].Image = fmt.Sprintf("old-version-%d", index)
|
||||||
|
|
@ -179,8 +179,8 @@ func TestSyncDeployment(t *testing.T) {
|
||||||
newRS.Spec.Replicas = pointer.Int32(test.newRSReplicas)
|
newRS.Spec.Replicas = pointer.Int32(test.newRSReplicas)
|
||||||
if strings.HasPrefix(name, "scale") {
|
if strings.HasPrefix(name, "scale") {
|
||||||
newRS.Annotations = map[string]string{
|
newRS.Annotations = map[string]string{
|
||||||
util.DesiredReplicasAnnotation: strconv.Itoa(-1),
|
util.ReplicasAnnotation: strconv.Itoa(-1),
|
||||||
util.MaxReplicasAnnotation: strconv.Itoa(int(test.dAvailable + test.maxSurge.IntVal)),
|
util.MaxReplicasAnnotation: strconv.Itoa(int(test.dAvailable + test.maxSurge.IntVal)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newRS.Status.Replicas = test.newRSReplicas
|
newRS.Status.Replicas = test.newRSReplicas
|
||||||
|
|
|
||||||
|
|
@ -270,9 +270,9 @@ func ScaleDownLimitForOld(oldRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, dep
|
||||||
klog.V(4).InfoS("Calculate scale down limit for ",
|
klog.V(4).InfoS("Calculate scale down limit for ",
|
||||||
"Deployment", klog.KObj(deployment),
|
"Deployment", klog.KObj(deployment),
|
||||||
// About the new replica set
|
// About the new replica set
|
||||||
"Replicas(New)", *(newRS.Spec.Replicas), "DesiredReplicas(New)", newRSDesiredCount,
|
"Replicas(New)", *(newRS.Spec.Replicas), "Replicas(New)", newRSDesiredCount,
|
||||||
// About the old replica sets
|
// About the old replica sets
|
||||||
"ReplicaS(Old)", oldPodsCount, "DesiredReplicas(Old)", oldRSDesiredCount, "ScaleDownLimit(Old)", scaleDownOldLimit,
|
"ReplicaS(Old)", oldPodsCount, "Replicas(Old)", oldRSDesiredCount, "ScaleDownLimit(Old)", scaleDownOldLimit,
|
||||||
// About the deployment
|
// About the deployment
|
||||||
"Replicas(Deployment)", *(deployment.Spec.Replicas), "Partition(Deployment)", newRSUpdateLimit)
|
"Replicas(Deployment)", *(deployment.Spec.Replicas), "Partition(Deployment)", newRSUpdateLimit)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -525,7 +525,7 @@ func (dc *DeploymentController) isScalingEvent(ctx context.Context, d *apps.Depl
|
||||||
}
|
}
|
||||||
allRSs := append(oldRSs, newRS)
|
allRSs := append(oldRSs, newRS)
|
||||||
for _, rs := range deploymentutil.FilterActiveReplicaSets(allRSs) {
|
for _, rs := range deploymentutil.FilterActiveReplicaSets(allRSs) {
|
||||||
desired, ok := deploymentutil.GetDesiredReplicasAnnotation(rs)
|
desired, ok := deploymentutil.GetReplicasAnnotation(rs)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,10 @@ const (
|
||||||
RevisionAnnotation = "deployment.kubernetes.io/revision"
|
RevisionAnnotation = "deployment.kubernetes.io/revision"
|
||||||
// RevisionHistoryAnnotation maintains the history of all old revisions that a replica set has served for a deployment.
|
// RevisionHistoryAnnotation maintains the history of all old revisions that a replica set has served for a deployment.
|
||||||
RevisionHistoryAnnotation = "deployment.kubernetes.io/revision-history"
|
RevisionHistoryAnnotation = "deployment.kubernetes.io/revision-history"
|
||||||
// DesiredReplicasAnnotation is the desired replicas for a deployment recorded as an annotation
|
// ReplicasAnnotation is the desired replicas for a deployment recorded as an annotation
|
||||||
// in its replica sets. Helps in separating scaling events from the rollout process and for
|
// in its replica sets. Helps in separating scaling events from the rollout process and for
|
||||||
// determining if the new replica set for a deployment is really saturated.
|
// determining if the new replica set for a deployment is really saturated.
|
||||||
DesiredReplicasAnnotation = "deployment.kubernetes.io/desired-replicas"
|
ReplicasAnnotation = "deployment.kubernetes.io/desired-replicas"
|
||||||
// MaxReplicasAnnotation is the maximum replicas a deployment can have at a given point, which
|
// MaxReplicasAnnotation is the maximum replicas a deployment can have at a given point, which
|
||||||
// is deployment.spec.replicas + maxSurge. Used by the underlying replica sets to estimate their
|
// is deployment.spec.replicas + maxSurge. Used by the underlying replica sets to estimate their
|
||||||
// proportions in case the deployment has surge replicas.
|
// proportions in case the deployment has surge replicas.
|
||||||
|
|
@ -267,7 +267,7 @@ var annotationsToSkip = map[string]bool{
|
||||||
v1.LastAppliedConfigAnnotation: true,
|
v1.LastAppliedConfigAnnotation: true,
|
||||||
RevisionAnnotation: true,
|
RevisionAnnotation: true,
|
||||||
RevisionHistoryAnnotation: true,
|
RevisionHistoryAnnotation: true,
|
||||||
DesiredReplicasAnnotation: true,
|
ReplicasAnnotation: true,
|
||||||
MaxReplicasAnnotation: true,
|
MaxReplicasAnnotation: true,
|
||||||
apps.DeprecatedRollbackTo: true,
|
apps.DeprecatedRollbackTo: true,
|
||||||
}
|
}
|
||||||
|
|
@ -325,9 +325,9 @@ func FindActiveOrLatest(newRS *apps.ReplicaSet, oldRSs []*apps.ReplicaSet) *apps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDesiredReplicasAnnotation returns the number of desired replicas
|
// GetReplicasAnnotation returns the number of desired replicas
|
||||||
func GetDesiredReplicasAnnotation(rs *apps.ReplicaSet) (int32, bool) {
|
func GetReplicasAnnotation(rs *apps.ReplicaSet) (int32, bool) {
|
||||||
return getIntFromAnnotation(rs, DesiredReplicasAnnotation)
|
return getIntFromAnnotation(rs, ReplicasAnnotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMaxReplicasAnnotation(rs *apps.ReplicaSet) (int32, bool) {
|
func getMaxReplicasAnnotation(rs *apps.ReplicaSet) (int32, bool) {
|
||||||
|
|
@ -354,8 +354,8 @@ func SetReplicasAnnotations(rs *apps.ReplicaSet, desiredReplicas, maxReplicas in
|
||||||
rs.Annotations = make(map[string]string)
|
rs.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
desiredString := fmt.Sprintf("%d", desiredReplicas)
|
desiredString := fmt.Sprintf("%d", desiredReplicas)
|
||||||
if hasString := rs.Annotations[DesiredReplicasAnnotation]; hasString != desiredString {
|
if hasString := rs.Annotations[ReplicasAnnotation]; hasString != desiredString {
|
||||||
rs.Annotations[DesiredReplicasAnnotation] = desiredString
|
rs.Annotations[ReplicasAnnotation] = desiredString
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
maxString := fmt.Sprintf("%d", maxReplicas)
|
maxString := fmt.Sprintf("%d", maxReplicas)
|
||||||
|
|
@ -372,7 +372,7 @@ func ReplicasAnnotationsNeedUpdate(rs *apps.ReplicaSet, desiredReplicas, maxRepl
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
desiredString := fmt.Sprintf("%d", desiredReplicas)
|
desiredString := fmt.Sprintf("%d", desiredReplicas)
|
||||||
if hasString := rs.Annotations[DesiredReplicasAnnotation]; hasString != desiredString {
|
if hasString := rs.Annotations[ReplicasAnnotation]; hasString != desiredString {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
maxString := fmt.Sprintf("%d", maxReplicas)
|
maxString := fmt.Sprintf("%d", maxReplicas)
|
||||||
|
|
@ -742,7 +742,7 @@ func IsSaturated(deployment *apps.Deployment, rs *apps.ReplicaSet) bool {
|
||||||
if rs == nil {
|
if rs == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
desiredString := rs.Annotations[DesiredReplicasAnnotation]
|
desiredString := rs.Annotations[ReplicasAnnotation]
|
||||||
desired, err := strconv.Atoi(desiredString)
|
desired, err := strconv.Atoi(desiredString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -1104,15 +1104,15 @@ func TestAnnotationUtils(t *testing.T) {
|
||||||
if !updated {
|
if !updated {
|
||||||
t.Errorf("SetReplicasAnnotations() failed")
|
t.Errorf("SetReplicasAnnotations() failed")
|
||||||
}
|
}
|
||||||
value, ok := tRS.Annotations[DesiredReplicasAnnotation]
|
value, ok := tRS.Annotations[ReplicasAnnotation]
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("SetReplicasAnnotations did not set DesiredReplicasAnnotation")
|
t.Errorf("SetReplicasAnnotations did not set ReplicasAnnotation")
|
||||||
}
|
}
|
||||||
if value != "10" {
|
if value != "10" {
|
||||||
t.Errorf("SetReplicasAnnotations did not set DesiredReplicasAnnotation correctly value=%s", value)
|
t.Errorf("SetReplicasAnnotations did not set ReplicasAnnotation correctly value=%s", value)
|
||||||
}
|
}
|
||||||
if value, ok = tRS.Annotations[MaxReplicasAnnotation]; !ok {
|
if value, ok = tRS.Annotations[MaxReplicasAnnotation]; !ok {
|
||||||
t.Errorf("SetReplicasAnnotations did not set DesiredReplicasAnnotation")
|
t.Errorf("SetReplicasAnnotations did not set ReplicasAnnotation")
|
||||||
}
|
}
|
||||||
if value != "11" {
|
if value != "11" {
|
||||||
t.Errorf("SetReplicasAnnotations did not set MaxReplicasAnnotation correctly value=%s", value)
|
t.Errorf("SetReplicasAnnotations did not set MaxReplicasAnnotation correctly value=%s", value)
|
||||||
|
|
@ -1120,7 +1120,7 @@ func TestAnnotationUtils(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
//Test Case 3: Check if annotations reflect deployments state
|
//Test Case 3: Check if annotations reflect deployments state
|
||||||
tRS.Annotations[DesiredReplicasAnnotation] = "1"
|
tRS.Annotations[ReplicasAnnotation] = "1"
|
||||||
tRS.Status.AvailableReplicas = 1
|
tRS.Status.AvailableReplicas = 1
|
||||||
tRS.Spec.Replicas = new(int32)
|
tRS.Spec.Replicas = new(int32)
|
||||||
*tRS.Spec.Replicas = 1
|
*tRS.Spec.Replicas = 1
|
||||||
|
|
@ -1160,7 +1160,7 @@ func TestReplicasAnnotationsNeedUpdate(t *testing.T) {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "hello",
|
Name: "hello",
|
||||||
Namespace: "test",
|
Namespace: "test",
|
||||||
Annotations: map[string]string{DesiredReplicasAnnotation: "8", MaxReplicasAnnotation: maxReplicas},
|
Annotations: map[string]string{ReplicasAnnotation: "8", MaxReplicasAnnotation: maxReplicas},
|
||||||
},
|
},
|
||||||
Spec: apps.ReplicaSetSpec{
|
Spec: apps.ReplicaSetSpec{
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
|
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
|
||||||
|
|
@ -1174,7 +1174,7 @@ func TestReplicasAnnotationsNeedUpdate(t *testing.T) {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "hello",
|
Name: "hello",
|
||||||
Namespace: "test",
|
Namespace: "test",
|
||||||
Annotations: map[string]string{DesiredReplicasAnnotation: desiredReplicas, MaxReplicasAnnotation: "16"},
|
Annotations: map[string]string{ReplicasAnnotation: desiredReplicas, MaxReplicasAnnotation: "16"},
|
||||||
},
|
},
|
||||||
Spec: apps.ReplicaSetSpec{
|
Spec: apps.ReplicaSetSpec{
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
|
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
|
||||||
|
|
@ -1188,7 +1188,7 @@ func TestReplicasAnnotationsNeedUpdate(t *testing.T) {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "hello",
|
Name: "hello",
|
||||||
Namespace: "test",
|
Namespace: "test",
|
||||||
Annotations: map[string]string{DesiredReplicasAnnotation: desiredReplicas, MaxReplicasAnnotation: maxReplicas},
|
Annotations: map[string]string{ReplicasAnnotation: desiredReplicas, MaxReplicasAnnotation: maxReplicas},
|
||||||
},
|
},
|
||||||
Spec: apps.ReplicaSetSpec{
|
Spec: apps.ReplicaSetSpec{
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
|
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/openkruise/rollouts/api/v1alpha1"
|
"github.com/openkruise/rollouts/api/v1alpha1"
|
||||||
|
|
@ -32,7 +31,6 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
"k8s.io/client-go/util/retry"
|
"k8s.io/client-go/util/retry"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
@ -70,7 +68,7 @@ func (m *canaryReleaseManager) runCanary(c *RolloutContext) error {
|
||||||
// When the first batch is trafficRouting rolling and the next steps are rolling release,
|
// When the first batch is trafficRouting rolling and the next steps are rolling release,
|
||||||
// We need to clean up the canary-related resources first and then rollout the rest of the batch.
|
// We need to clean up the canary-related resources first and then rollout the rest of the batch.
|
||||||
currentStep := c.Rollout.Spec.Strategy.Canary.Steps[canaryStatus.CurrentStepIndex-1]
|
currentStep := c.Rollout.Spec.Strategy.Canary.Steps[canaryStatus.CurrentStepIndex-1]
|
||||||
if currentStep.Weight == nil && len(currentStep.Matches) == 0 {
|
if currentStep.Traffic == nil && len(currentStep.Matches) == 0 {
|
||||||
tr := newTrafficRoutingContext(c)
|
tr := newTrafficRoutingContext(c)
|
||||||
done, err := m.trafficRoutingManager.FinalisingTrafficRouting(tr, false)
|
done, err := m.trafficRoutingManager.FinalisingTrafficRouting(tr, false)
|
||||||
c.NewStatus.CanaryStatus.LastUpdateTime = tr.LastUpdateTime
|
c.NewStatus.CanaryStatus.LastUpdateTime = tr.LastUpdateTime
|
||||||
|
|
@ -201,8 +199,7 @@ func (m *canaryReleaseManager) doCanaryPaused(c *RolloutContext) (bool, error) {
|
||||||
steps := len(c.Rollout.Spec.Strategy.Canary.Steps)
|
steps := len(c.Rollout.Spec.Strategy.Canary.Steps)
|
||||||
// If it is the last step, and 100% of pods, then return true
|
// If it is the last step, and 100% of pods, then return true
|
||||||
if int32(steps) == canaryStatus.CurrentStepIndex {
|
if int32(steps) == canaryStatus.CurrentStepIndex {
|
||||||
if currentStep.Weight != nil && *currentStep.Weight == 100 ||
|
if currentStep.Replicas != nil && currentStep.Replicas.StrVal == "100%" {
|
||||||
currentStep.Replicas != nil && currentStep.Replicas.StrVal == "100%" {
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -276,7 +273,7 @@ func (m *canaryReleaseManager) removeRolloutProgressingAnnotation(c *RolloutCont
|
||||||
if _, ok := c.Workload.Annotations[util.InRolloutProgressingAnnotation]; !ok {
|
if _, ok := c.Workload.Annotations[util.InRolloutProgressingAnnotation]; !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
workloadRef := c.Rollout.Spec.ObjectRef.WorkloadRef
|
workloadRef := c.Rollout.Spec.WorkloadRef
|
||||||
workloadGVK := schema.FromAPIVersionAndKind(workloadRef.APIVersion, workloadRef.Kind)
|
workloadGVK := schema.FromAPIVersionAndKind(workloadRef.APIVersion, workloadRef.Kind)
|
||||||
obj := util.GetEmptyWorkloadObject(workloadGVK)
|
obj := util.GetEmptyWorkloadObject(workloadGVK)
|
||||||
obj.SetNamespace(c.Workload.Namespace)
|
obj.SetNamespace(c.Workload.Namespace)
|
||||||
|
|
@ -313,7 +310,6 @@ func (m *canaryReleaseManager) runBatchRelease(rollout *v1beta1.Rollout, rollout
|
||||||
klog.Infof("rollout(%s/%s) do batchRelease batch(%d) success", rollout.Namespace, rollout.Name, batch+1)
|
klog.Infof("rollout(%s/%s) do batchRelease batch(%d) success", rollout.Namespace, rollout.Name, batch+1)
|
||||||
return true, br, nil
|
return true, br, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// update batchRelease to the latest version
|
// update batchRelease to the latest version
|
||||||
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||||
if err = m.Get(context.TODO(), client.ObjectKey{Namespace: newBr.Namespace, Name: newBr.Name}, br); err != nil {
|
if err = m.Get(context.TODO(), client.ObjectKey{Namespace: newBr.Namespace, Name: newBr.Name}, br); err != nil {
|
||||||
|
|
@ -341,11 +337,7 @@ func (m *canaryReleaseManager) fetchBatchRelease(ns, name string) (*v1beta1.Batc
|
||||||
func createBatchRelease(rollout *v1beta1.Rollout, rolloutID string, batch int32, isRollback bool) *v1beta1.BatchRelease {
|
func createBatchRelease(rollout *v1beta1.Rollout, rolloutID string, batch int32, isRollback bool) *v1beta1.BatchRelease {
|
||||||
var batches []v1beta1.ReleaseBatch
|
var batches []v1beta1.ReleaseBatch
|
||||||
for _, step := range rollout.Spec.Strategy.Canary.Steps {
|
for _, step := range rollout.Spec.Strategy.Canary.Steps {
|
||||||
if step.Replicas == nil {
|
batches = append(batches, v1beta1.ReleaseBatch{CanaryReplicas: *step.Replicas})
|
||||||
batches = append(batches, v1beta1.ReleaseBatch{CanaryReplicas: intstr.FromString(strconv.Itoa(int(*step.Weight)) + "%")})
|
|
||||||
} else {
|
|
||||||
batches = append(batches, v1beta1.ReleaseBatch{CanaryReplicas: *step.Replicas})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
br := &v1beta1.BatchRelease{
|
br := &v1beta1.BatchRelease{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
|
@ -354,19 +346,18 @@ func createBatchRelease(rollout *v1beta1.Rollout, rolloutID string, batch int32,
|
||||||
OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(rollout, rolloutControllerKind)},
|
OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(rollout, rolloutControllerKind)},
|
||||||
},
|
},
|
||||||
Spec: v1beta1.BatchReleaseSpec{
|
Spec: v1beta1.BatchReleaseSpec{
|
||||||
TargetRef: v1beta1.ObjectRef{
|
WorkloadRef: v1beta1.ObjectRef{
|
||||||
WorkloadRef: &v1beta1.WorkloadRef{
|
APIVersion: rollout.Spec.WorkloadRef.APIVersion,
|
||||||
APIVersion: rollout.Spec.ObjectRef.WorkloadRef.APIVersion,
|
Kind: rollout.Spec.WorkloadRef.Kind,
|
||||||
Kind: rollout.Spec.ObjectRef.WorkloadRef.Kind,
|
Name: rollout.Spec.WorkloadRef.Name,
|
||||||
Name: rollout.Spec.ObjectRef.WorkloadRef.Name,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
ReleasePlan: v1beta1.ReleasePlan{
|
ReleasePlan: v1beta1.ReleasePlan{
|
||||||
Batches: batches,
|
Batches: batches,
|
||||||
RolloutID: rolloutID,
|
RolloutID: rolloutID,
|
||||||
BatchPartition: utilpointer.Int32Ptr(batch),
|
BatchPartition: utilpointer.Int32Ptr(batch),
|
||||||
FailureThreshold: rollout.Spec.Strategy.Canary.FailureThreshold,
|
FailureThreshold: rollout.Spec.Strategy.Canary.FailureThreshold,
|
||||||
PatchPodTemplateMetadata: rollout.Spec.Strategy.Canary.PatchPodTemplateMetadata,
|
PatchPodTemplateMetadata: rollout.Spec.Strategy.Canary.PatchPodTemplateMetadata,
|
||||||
|
EnableExtraWorkloadForCanary: rollout.Spec.Strategy.Canary.EnableExtraWorkloadForCanary,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -374,9 +365,6 @@ func createBatchRelease(rollout *v1beta1.Rollout, rolloutID string, batch int32,
|
||||||
if isRollback {
|
if isRollback {
|
||||||
annotations[v1alpha1.RollbackInBatchAnnotation] = rollout.Annotations[v1alpha1.RollbackInBatchAnnotation]
|
annotations[v1alpha1.RollbackInBatchAnnotation] = rollout.Annotations[v1alpha1.RollbackInBatchAnnotation]
|
||||||
}
|
}
|
||||||
if style, ok := rollout.Annotations[v1beta1.RolloutStyleAnnotation]; ok {
|
|
||||||
annotations[v1beta1.RolloutStyleAnnotation] = style
|
|
||||||
}
|
|
||||||
if len(annotations) > 0 {
|
if len(annotations) > 0 {
|
||||||
br.Annotations = annotations
|
br.Annotations = annotations
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ func TestRunCanary(t *testing.T) {
|
||||||
},
|
},
|
||||||
getRollout: func() (*v1beta1.Rollout, *v1beta1.BatchRelease) {
|
getRollout: func() (*v1beta1.Rollout, *v1beta1.BatchRelease) {
|
||||||
obj := rolloutDemo.DeepCopy()
|
obj := rolloutDemo.DeepCopy()
|
||||||
|
obj.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true
|
||||||
obj.Status.CanaryStatus.ObservedWorkloadGeneration = 2
|
obj.Status.CanaryStatus.ObservedWorkloadGeneration = 2
|
||||||
obj.Status.CanaryStatus.RolloutHash = "f55bvd874d5f2fzvw46bv966x4bwbdv4wx6bd9f7b46ww788954b8z8w29b7wxfd"
|
obj.Status.CanaryStatus.RolloutHash = "f55bvd874d5f2fzvw46bv966x4bwbdv4wx6bd9f7b46ww788954b8z8w29b7wxfd"
|
||||||
obj.Status.CanaryStatus.StableRevision = "pod-template-hash-v1"
|
obj.Status.CanaryStatus.StableRevision = "pod-template-hash-v1"
|
||||||
|
|
@ -98,6 +99,7 @@ func TestRunCanary(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
br.Spec.ReleasePlan.BatchPartition = utilpointer.Int32(0)
|
br.Spec.ReleasePlan.BatchPartition = utilpointer.Int32(0)
|
||||||
|
br.Spec.ReleasePlan.EnableExtraWorkloadForCanary = true
|
||||||
return br
|
return br
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -130,6 +132,7 @@ func TestRunCanary(t *testing.T) {
|
||||||
},
|
},
|
||||||
getRollout: func() (*v1beta1.Rollout, *v1beta1.BatchRelease) {
|
getRollout: func() (*v1beta1.Rollout, *v1beta1.BatchRelease) {
|
||||||
obj := rolloutDemo.DeepCopy()
|
obj := rolloutDemo.DeepCopy()
|
||||||
|
obj.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true
|
||||||
obj.Status.CanaryStatus.ObservedWorkloadGeneration = 2
|
obj.Status.CanaryStatus.ObservedWorkloadGeneration = 2
|
||||||
obj.Status.CanaryStatus.RolloutHash = "f55bvd874d5f2fzvw46bv966x4bwbdv4wx6bd9f7b46ww788954b8z8w29b7wxfd"
|
obj.Status.CanaryStatus.RolloutHash = "f55bvd874d5f2fzvw46bv966x4bwbdv4wx6bd9f7b46ww788954b8z8w29b7wxfd"
|
||||||
obj.Status.CanaryStatus.StableRevision = "pod-template-hash-v1"
|
obj.Status.CanaryStatus.StableRevision = "pod-template-hash-v1"
|
||||||
|
|
@ -155,9 +158,10 @@ func TestRunCanary(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
br.Spec.ReleasePlan.BatchPartition = utilpointer.Int32(0)
|
br.Spec.ReleasePlan.BatchPartition = utilpointer.Int32(0)
|
||||||
|
br.Spec.ReleasePlan.EnableExtraWorkloadForCanary = true
|
||||||
br.Status = v1beta1.BatchReleaseStatus{
|
br.Status = v1beta1.BatchReleaseStatus{
|
||||||
ObservedGeneration: 1,
|
ObservedGeneration: 1,
|
||||||
ObservedReleasePlanHash: "6d6a40791161e88ec0483688e951b589a4cbd0bf351974827706b79f99378fd5",
|
ObservedReleasePlanHash: "d444a1007776da957d7d8549e3375c96179621b85670ad1e2bb0fc5fea16446a",
|
||||||
CanaryStatus: v1beta1.BatchReleaseCanaryStatus{
|
CanaryStatus: v1beta1.BatchReleaseCanaryStatus{
|
||||||
CurrentBatchState: v1beta1.ReadyBatchState,
|
CurrentBatchState: v1beta1.ReadyBatchState,
|
||||||
CurrentBatch: 0,
|
CurrentBatch: 0,
|
||||||
|
|
@ -200,6 +204,7 @@ func TestRunCanary(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
br.Spec.ReleasePlan.BatchPartition = utilpointer.Int32(0)
|
br.Spec.ReleasePlan.BatchPartition = utilpointer.Int32(0)
|
||||||
|
br.Spec.ReleasePlan.EnableExtraWorkloadForCanary = true
|
||||||
return br
|
return br
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -111,10 +111,10 @@ func (r *RolloutReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
|
||||||
klog.Infof("Begin to reconcile Rollout %v", klog.KObj(rollout))
|
klog.Infof("Begin to reconcile Rollout %v", klog.KObj(rollout))
|
||||||
|
|
||||||
// If workload watcher does not exist, then add the watcher dynamically
|
// If workload watcher does not exist, then add the watcher dynamically
|
||||||
workloadRef := rollout.Spec.ObjectRef.WorkloadRef
|
workloadRef := rollout.Spec.WorkloadRef
|
||||||
workloadGVK := util.GetGVKFrom(workloadRef)
|
workloadGVK := util.GetGVKFrom(&workloadRef)
|
||||||
_, exists := watchedWorkload.Load(workloadGVK.String())
|
_, exists := watchedWorkload.Load(workloadGVK.String())
|
||||||
if workloadRef != nil && !exists {
|
if !exists {
|
||||||
succeeded, err := util.AddWatcherDynamically(runtimeController, workloadHandler, workloadGVK)
|
succeeded, err := util.AddWatcherDynamically(runtimeController, workloadHandler, workloadGVK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
|
|
|
||||||
|
|
@ -48,45 +48,44 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Spec: v1beta1.RolloutSpec{
|
Spec: v1beta1.RolloutSpec{
|
||||||
ObjectRef: v1beta1.ObjectRef{
|
WorkloadRef: v1beta1.ObjectRef{
|
||||||
WorkloadRef: &v1beta1.WorkloadRef{
|
APIVersion: "apps/v1",
|
||||||
APIVersion: "apps/v1",
|
Kind: "Deployment",
|
||||||
Kind: "Deployment",
|
Name: "echoserver",
|
||||||
Name: "echoserver",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Strategy: v1beta1.RolloutStrategy{
|
Strategy: v1beta1.RolloutStrategy{
|
||||||
Canary: &v1beta1.CanaryStrategy{
|
Canary: &v1beta1.CanaryStrategy{
|
||||||
|
EnableExtraWorkloadForCanary: true,
|
||||||
Steps: []v1beta1.CanaryStep{
|
Steps: []v1beta1.CanaryStep{
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(5),
|
Traffic: utilpointer.String("5%"),
|
||||||
},
|
},
|
||||||
Replicas: &intstr.IntOrString{IntVal: 1},
|
Replicas: &intstr.IntOrString{IntVal: 1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(20),
|
Traffic: utilpointer.String("20%"),
|
||||||
},
|
},
|
||||||
Replicas: &intstr.IntOrString{IntVal: 2},
|
Replicas: &intstr.IntOrString{IntVal: 2},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(60),
|
Traffic: utilpointer.String("60%"),
|
||||||
},
|
},
|
||||||
Replicas: &intstr.IntOrString{IntVal: 6},
|
Replicas: &intstr.IntOrString{IntVal: 6},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(100),
|
Traffic: utilpointer.String("100%"),
|
||||||
},
|
},
|
||||||
Replicas: &intstr.IntOrString{IntVal: 10},
|
Replicas: &intstr.IntOrString{IntVal: 10},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TrafficRoutings: []v1alpha1.TrafficRoutingRef{
|
TrafficRoutings: []v1beta1.TrafficRoutingRef{
|
||||||
{
|
{
|
||||||
Service: "echoserver",
|
Service: "echoserver",
|
||||||
Ingress: &v1alpha1.IngressTrafficRouting{
|
Ingress: &v1beta1.IngressTrafficRouting{
|
||||||
Name: "echoserver",
|
Name: "echoserver",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -207,12 +206,10 @@ var (
|
||||||
Generation: 1,
|
Generation: 1,
|
||||||
},
|
},
|
||||||
Spec: v1beta1.BatchReleaseSpec{
|
Spec: v1beta1.BatchReleaseSpec{
|
||||||
TargetRef: v1beta1.ObjectRef{
|
WorkloadRef: v1beta1.ObjectRef{
|
||||||
WorkloadRef: &v1beta1.WorkloadRef{
|
APIVersion: "apps/v1",
|
||||||
APIVersion: "apps/v1",
|
Kind: "Deployment",
|
||||||
Kind: "Deployment",
|
Name: "echoserver",
|
||||||
Name: "echoserver",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Status: v1beta1.BatchReleaseStatus{},
|
Status: v1beta1.BatchReleaseStatus{},
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ func (w *enqueueRequestForWorkload) getRolloutForWorkload(key types.NamespacedNa
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rollout := range rList.Items {
|
for _, rollout := range rList.Items {
|
||||||
targetRef := rollout.Spec.ObjectRef.WorkloadRef
|
targetRef := rollout.Spec.WorkloadRef
|
||||||
targetGV, err := schema.ParseGroupVersion(targetRef.APIVersion)
|
targetGV, err := schema.ParseGroupVersion(targetRef.APIVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("failed to parse rollout(%s/%s) targetRef's group version: %s", rollout.Namespace, rollout.Name, targetRef.APIVersion)
|
klog.Errorf("failed to parse rollout(%s/%s) targetRef's group version: %s", rollout.Namespace, rollout.Name, targetRef.APIVersion)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package rollout
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/openkruise/rollouts/api/v1alpha1"
|
"github.com/openkruise/rollouts/api/v1alpha1"
|
||||||
|
|
@ -391,12 +390,7 @@ func (r *RolloutReconciler) recalculateCanaryStep(c *RolloutContext) (int32, err
|
||||||
for i := range c.Rollout.Spec.Strategy.Canary.Steps {
|
for i := range c.Rollout.Spec.Strategy.Canary.Steps {
|
||||||
step := c.Rollout.Spec.Strategy.Canary.Steps[i]
|
step := c.Rollout.Spec.Strategy.Canary.Steps[i]
|
||||||
var desiredReplicas int
|
var desiredReplicas int
|
||||||
if step.Replicas != nil {
|
desiredReplicas, _ = intstr.GetScaledValueFromIntOrPercent(step.Replicas, int(c.Workload.Replicas), true)
|
||||||
desiredReplicas, _ = intstr.GetScaledValueFromIntOrPercent(step.Replicas, int(c.Workload.Replicas), true)
|
|
||||||
} else {
|
|
||||||
replicas := intstr.FromString(strconv.Itoa(int(*step.Weight)) + "%")
|
|
||||||
desiredReplicas, _ = intstr.GetScaledValueFromIntOrPercent(&replicas, int(c.Workload.Replicas), true)
|
|
||||||
}
|
|
||||||
stepIndex = int32(i + 1)
|
stepIndex = int32(i + 1)
|
||||||
if currentReplicas <= desiredReplicas {
|
if currentReplicas <= desiredReplicas {
|
||||||
break
|
break
|
||||||
|
|
|
||||||
|
|
@ -660,19 +660,13 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
|
||||||
obj := rolloutDemo.DeepCopy()
|
obj := rolloutDemo.DeepCopy()
|
||||||
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
|
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "20%"},
|
||||||
Weight: utilpointer.Int32(20),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "50%"},
|
||||||
Weight: utilpointer.Int32(50),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "100%"},
|
||||||
Weight: utilpointer.Int32(100),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return obj
|
return obj
|
||||||
|
|
@ -705,19 +699,13 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
|
||||||
obj := rolloutDemo.DeepCopy()
|
obj := rolloutDemo.DeepCopy()
|
||||||
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
|
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "20%"},
|
||||||
Weight: utilpointer.Int32(20),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "40%"},
|
||||||
Weight: utilpointer.Int32(40),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "100%"},
|
||||||
Weight: utilpointer.Int32(100),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return obj
|
return obj
|
||||||
|
|
@ -750,19 +738,13 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
|
||||||
obj := rolloutDemo.DeepCopy()
|
obj := rolloutDemo.DeepCopy()
|
||||||
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
|
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "40%"},
|
||||||
Weight: utilpointer.Int32(40),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "60%"},
|
||||||
Weight: utilpointer.Int32(60),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "100%"},
|
||||||
Weight: utilpointer.Int32(100),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return obj
|
return obj
|
||||||
|
|
@ -795,19 +777,13 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
|
||||||
obj := rolloutDemo.DeepCopy()
|
obj := rolloutDemo.DeepCopy()
|
||||||
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
|
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "10%"},
|
||||||
Weight: utilpointer.Int32(10),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "30%"},
|
||||||
Weight: utilpointer.Int32(30),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
Replicas: &intstr.IntOrString{Type: intstr.String, StrVal: "100%"},
|
||||||
Weight: utilpointer.Int32(100),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return obj
|
return obj
|
||||||
|
|
@ -840,8 +816,8 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
|
||||||
obj := rolloutDemo.DeepCopy()
|
obj := rolloutDemo.DeepCopy()
|
||||||
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
|
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(2),
|
Traffic: utilpointer.String("2%"),
|
||||||
},
|
},
|
||||||
Replicas: &intstr.IntOrString{
|
Replicas: &intstr.IntOrString{
|
||||||
Type: intstr.String,
|
Type: intstr.String,
|
||||||
|
|
@ -849,8 +825,8 @@ func TestReCalculateCanaryStepIndex(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(3),
|
Traffic: utilpointer.String("3%"),
|
||||||
},
|
},
|
||||||
Replicas: &intstr.IntOrString{
|
Replicas: &intstr.IntOrString{
|
||||||
Type: intstr.String,
|
Type: intstr.String,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/openkruise/rollouts/api/v1alpha1"
|
|
||||||
"github.com/openkruise/rollouts/api/v1beta1"
|
"github.com/openkruise/rollouts/api/v1beta1"
|
||||||
"github.com/openkruise/rollouts/pkg/trafficrouting"
|
"github.com/openkruise/rollouts/pkg/trafficrouting"
|
||||||
"github.com/openkruise/rollouts/pkg/util"
|
"github.com/openkruise/rollouts/pkg/util"
|
||||||
|
|
@ -43,7 +42,7 @@ func TestCalculateRolloutHash(t *testing.T) {
|
||||||
return obj
|
return obj
|
||||||
},
|
},
|
||||||
expectHash: func() string {
|
expectHash: func() string {
|
||||||
return "626fd556c5d5v2d9b4f7c2xvbc9dxddxzd48xvb9w9wfcdvdz6v959fbzd84b57x"
|
return "75746v7d5z9x59v5c7dff4wd9cv9cc28czf6c2z664w7zbb7vw2bzv76v99z6bd9"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -56,7 +55,7 @@ func TestCalculateRolloutHash(t *testing.T) {
|
||||||
return obj
|
return obj
|
||||||
},
|
},
|
||||||
expectHash: func() string {
|
expectHash: func() string {
|
||||||
return "626fd556c5d5v2d9b4f7c2xvbc9dxddxzd48xvb9w9wfcdvdz6v959fbzd84b57x"
|
return "75746v7d5z9x59v5c7dff4wd9cv9cc28czf6c2z664w7zbb7vw2bzv76v99z6bd9"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -65,20 +64,20 @@ func TestCalculateRolloutHash(t *testing.T) {
|
||||||
obj := rolloutDemo.DeepCopy()
|
obj := rolloutDemo.DeepCopy()
|
||||||
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
|
obj.Spec.Strategy.Canary.Steps = []v1beta1.CanaryStep{
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(50),
|
Traffic: utilpointer.String("50%"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(100),
|
Traffic: utilpointer.String("100%"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return obj
|
return obj
|
||||||
},
|
},
|
||||||
expectHash: func() string {
|
expectHash: func() string {
|
||||||
return "8c449wxc46x8dd764x4v4wzvc7454f48478vd9db27fv8v9dw5cwbcb6b42b75dc"
|
return "db9c2x47d282c84z6684d598bzwf9b4x6ffb45fc456xdfv97945v2vb79w72c7z"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -215,12 +215,15 @@ func (r *TrafficRoutingReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTrafficRoutingContext(tr *v1alpha1.TrafficRouting) *trafficrouting.TrafficRoutingContext {
|
func newTrafficRoutingContext(tr *v1alpha1.TrafficRouting) *trafficrouting.TrafficRoutingContext {
|
||||||
return &trafficrouting.TrafficRoutingContext{
|
c := &trafficrouting.TrafficRoutingContext{
|
||||||
Key: fmt.Sprintf("TrafficRouting(%s/%s)", tr.Namespace, tr.Name),
|
Key: fmt.Sprintf("TrafficRouting(%s/%s)", tr.Namespace, tr.Name),
|
||||||
Namespace: tr.Namespace,
|
Namespace: tr.Namespace,
|
||||||
ObjectRef: tr.Spec.ObjectRef,
|
Strategy: v1alpha1.ConversionToV1beta1TrafficRoutingStrategy(tr.Spec.Strategy),
|
||||||
Strategy: tr.Spec.Strategy,
|
|
||||||
OwnerRef: *metav1.NewControllerRef(tr, trControllerKind),
|
OwnerRef: *metav1.NewControllerRef(tr, trControllerKind),
|
||||||
OnlyTrafficRouting: true,
|
OnlyTrafficRouting: true,
|
||||||
}
|
}
|
||||||
|
for _, ref := range tr.Spec.ObjectRef {
|
||||||
|
c.ObjectRef = append(c.ObjectRef, v1alpha1.ConversionToV1beta1TrafficRoutingRef(ref))
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/openkruise/rollouts/api/v1alpha1"
|
"github.com/openkruise/rollouts/api/v1beta1"
|
||||||
"github.com/openkruise/rollouts/pkg/trafficrouting/network"
|
"github.com/openkruise/rollouts/pkg/trafficrouting/network"
|
||||||
custom "github.com/openkruise/rollouts/pkg/trafficrouting/network/customNetworkProvider"
|
custom "github.com/openkruise/rollouts/pkg/trafficrouting/network/customNetworkProvider"
|
||||||
"github.com/openkruise/rollouts/pkg/trafficrouting/network/gateway"
|
"github.com/openkruise/rollouts/pkg/trafficrouting/network/gateway"
|
||||||
|
|
@ -44,8 +44,8 @@ type TrafficRoutingContext struct {
|
||||||
// only for log info
|
// only for log info
|
||||||
Key string
|
Key string
|
||||||
Namespace string
|
Namespace string
|
||||||
ObjectRef []v1alpha1.TrafficRoutingRef
|
ObjectRef []v1beta1.TrafficRoutingRef
|
||||||
Strategy v1alpha1.TrafficRoutingStrategy
|
Strategy v1beta1.TrafficRoutingStrategy
|
||||||
// OnlyTrafficRouting
|
// OnlyTrafficRouting
|
||||||
OnlyTrafficRouting bool
|
OnlyTrafficRouting bool
|
||||||
OwnerRef metav1.OwnerReference
|
OwnerRef metav1.OwnerReference
|
||||||
|
|
@ -100,7 +100,7 @@ func (m *Manager) DoTrafficRouting(c *TrafficRoutingContext) (bool, error) {
|
||||||
if trafficRouting.GracePeriodSeconds <= 0 {
|
if trafficRouting.GracePeriodSeconds <= 0 {
|
||||||
trafficRouting.GracePeriodSeconds = defaultGracePeriodSeconds
|
trafficRouting.GracePeriodSeconds = defaultGracePeriodSeconds
|
||||||
}
|
}
|
||||||
if c.Strategy.Weight == nil && len(c.Strategy.Matches) == 0 {
|
if c.Strategy.Traffic == nil && len(c.Strategy.Matches) == 0 {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,7 +228,7 @@ func (m *Manager) FinalisingTrafficRouting(c *TrafficRoutingContext, onlyRestore
|
||||||
}
|
}
|
||||||
|
|
||||||
// First route 100% traffic to stable service
|
// First route 100% traffic to stable service
|
||||||
c.Strategy.Weight = utilpointer.Int32(0)
|
c.Strategy.Traffic = utilpointer.StringPtr("0%")
|
||||||
verify, err = trController.EnsureRoutes(context.TODO(), &c.Strategy)
|
verify, err = trController.EnsureRoutes(context.TODO(), &c.Strategy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
|
||||||
|
|
@ -113,45 +113,43 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Spec: v1beta1.RolloutSpec{
|
Spec: v1beta1.RolloutSpec{
|
||||||
ObjectRef: v1beta1.ObjectRef{
|
WorkloadRef: v1beta1.ObjectRef{
|
||||||
WorkloadRef: &v1beta1.WorkloadRef{
|
APIVersion: "apps/v1",
|
||||||
APIVersion: "apps/v1",
|
Kind: "Deployment",
|
||||||
Kind: "Deployment",
|
Name: "echoserver",
|
||||||
Name: "echoserver",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Strategy: v1beta1.RolloutStrategy{
|
Strategy: v1beta1.RolloutStrategy{
|
||||||
Canary: &v1beta1.CanaryStrategy{
|
Canary: &v1beta1.CanaryStrategy{
|
||||||
Steps: []v1beta1.CanaryStep{
|
Steps: []v1beta1.CanaryStep{
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(5),
|
Traffic: utilpointer.String("5%"),
|
||||||
},
|
},
|
||||||
Replicas: &intstr.IntOrString{IntVal: 1},
|
Replicas: &intstr.IntOrString{IntVal: 1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(20),
|
Traffic: utilpointer.String("20%"),
|
||||||
},
|
},
|
||||||
Replicas: &intstr.IntOrString{IntVal: 2},
|
Replicas: &intstr.IntOrString{IntVal: 2},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(60),
|
Traffic: utilpointer.String("60%"),
|
||||||
},
|
},
|
||||||
Replicas: &intstr.IntOrString{IntVal: 6},
|
Replicas: &intstr.IntOrString{IntVal: 6},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: v1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(100),
|
Traffic: utilpointer.String("100%"),
|
||||||
},
|
},
|
||||||
Replicas: &intstr.IntOrString{IntVal: 10},
|
Replicas: &intstr.IntOrString{IntVal: 10},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TrafficRoutings: []v1alpha1.TrafficRoutingRef{
|
TrafficRoutings: []v1beta1.TrafficRoutingRef{
|
||||||
{
|
{
|
||||||
Service: "echoserver",
|
Service: "echoserver",
|
||||||
Ingress: &v1alpha1.IngressTrafficRouting{
|
Ingress: &v1beta1.IngressTrafficRouting{
|
||||||
Name: "echoserver",
|
Name: "echoserver",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -23,20 +23,20 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"github.com/openkruise/rollouts/api/v1beta1"
|
||||||
"k8s.io/klog/v2"
|
|
||||||
|
|
||||||
rolloutv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
|
||||||
"github.com/openkruise/rollouts/pkg/trafficrouting/network"
|
"github.com/openkruise/rollouts/pkg/trafficrouting/network"
|
||||||
"github.com/openkruise/rollouts/pkg/util"
|
"github.com/openkruise/rollouts/pkg/util"
|
||||||
"github.com/openkruise/rollouts/pkg/util/configuration"
|
"github.com/openkruise/rollouts/pkg/util/configuration"
|
||||||
"github.com/openkruise/rollouts/pkg/util/luamanager"
|
"github.com/openkruise/rollouts/pkg/util/luamanager"
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
utilpointer "k8s.io/utils/pointer"
|
utilpointer "k8s.io/utils/pointer"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
)
|
)
|
||||||
|
|
@ -50,7 +50,7 @@ type LuaData struct {
|
||||||
Data Data
|
Data Data
|
||||||
CanaryWeight int32
|
CanaryWeight int32
|
||||||
StableWeight int32
|
StableWeight int32
|
||||||
Matches []rolloutv1alpha1.HttpRouteMatch
|
Matches []v1beta1.HttpRouteMatch
|
||||||
CanaryService string
|
CanaryService string
|
||||||
StableService string
|
StableService string
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +72,7 @@ type Config struct {
|
||||||
CanaryService string
|
CanaryService string
|
||||||
StableService string
|
StableService string
|
||||||
// network providers need to be created
|
// network providers need to be created
|
||||||
TrafficConf []rolloutv1alpha1.CustomNetworkRef
|
TrafficConf []v1beta1.ObjectRef
|
||||||
OwnerRef metav1.OwnerReference
|
OwnerRef metav1.OwnerReference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,11 +107,11 @@ func (r *customController) Initialize(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// when ensuring routes, first execute lua for all custom providers, then update
|
// when ensuring routes, first execute lua for all custom providers, then update
|
||||||
func (r *customController) EnsureRoutes(ctx context.Context, strategy *rolloutv1alpha1.TrafficRoutingStrategy) (bool, error) {
|
func (r *customController) EnsureRoutes(ctx context.Context, strategy *v1beta1.TrafficRoutingStrategy) (bool, error) {
|
||||||
done := true
|
done := true
|
||||||
// *strategy.Weight == 0 indicates traffic routing is doing finalising and tries to route whole traffic to stable service
|
// *strategy.Weight == 0 indicates traffic routing is doing finalising and tries to route whole traffic to stable service
|
||||||
// then directly do finalising
|
// then directly do finalising
|
||||||
if strategy.Weight != nil && *strategy.Weight == 0 {
|
if strategy.Traffic != nil && *strategy.Traffic == "0%" {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -254,8 +254,13 @@ func (r *customController) restoreObject(obj *unstructured.Unstructured) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *customController) executeLuaForCanary(spec Data, strategy *rolloutv1alpha1.TrafficRoutingStrategy, luaScript string) (Data, error) {
|
func (r *customController) executeLuaForCanary(spec Data, strategy *v1beta1.TrafficRoutingStrategy, luaScript string) (Data, error) {
|
||||||
weight := strategy.Weight
|
var weight *int32
|
||||||
|
if strategy.Traffic != nil {
|
||||||
|
is := intstr.FromString(*strategy.Traffic)
|
||||||
|
weightInt, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
|
||||||
|
weight = utilpointer.Int32(int32(weightInt))
|
||||||
|
}
|
||||||
matches := strategy.Matches
|
matches := strategy.Matches
|
||||||
if weight == nil {
|
if weight == nil {
|
||||||
// the lua script does not have a pointer type,
|
// the lua script does not have a pointer type,
|
||||||
|
|
@ -296,7 +301,7 @@ func (r *customController) executeLuaForCanary(spec Data, strategy *rolloutv1alp
|
||||||
return Data{}, fmt.Errorf("expect table output from Lua script, not %s", returnValue.Type().String())
|
return Data{}, fmt.Errorf("expect table output from Lua script, not %s", returnValue.Type().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *customController) getLuaScript(ctx context.Context, ref rolloutv1alpha1.CustomNetworkRef) (string, error) {
|
func (r *customController) getLuaScript(ctx context.Context, ref v1beta1.ObjectRef) (string, error) {
|
||||||
// get local lua script
|
// get local lua script
|
||||||
// luaScript.Provider: CRDGroupt/Kind
|
// luaScript.Provider: CRDGroupt/Kind
|
||||||
group := strings.Split(ref.APIVersion, "/")[0]
|
group := strings.Split(ref.APIVersion, "/")[0]
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import (
|
||||||
|
|
||||||
rolloutapi "github.com/openkruise/rollouts/api"
|
rolloutapi "github.com/openkruise/rollouts/api"
|
||||||
rolloutsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
rolloutsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
||||||
rolloutsv1beta1 "github.com/openkruise/rollouts/api/v1beta1"
|
"github.com/openkruise/rollouts/api/v1beta1"
|
||||||
"github.com/openkruise/rollouts/pkg/util"
|
"github.com/openkruise/rollouts/pkg/util"
|
||||||
"github.com/openkruise/rollouts/pkg/util/configuration"
|
"github.com/openkruise/rollouts/pkg/util/configuration"
|
||||||
"github.com/openkruise/rollouts/pkg/util/luamanager"
|
"github.com/openkruise/rollouts/pkg/util/luamanager"
|
||||||
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
utilpointer "k8s.io/utils/pointer"
|
utilpointer "k8s.io/utils/pointer"
|
||||||
|
|
@ -141,7 +142,7 @@ func TestInitialize(t *testing.T) {
|
||||||
return Config{
|
return Config{
|
||||||
StableService: "echoserver",
|
StableService: "echoserver",
|
||||||
CanaryService: "echoserver-canary",
|
CanaryService: "echoserver-canary",
|
||||||
TrafficConf: []rolloutsv1alpha1.CustomNetworkRef{
|
TrafficConf: []v1beta1.ObjectRef{
|
||||||
{
|
{
|
||||||
APIVersion: "networking.istio.io/v1alpha3",
|
APIVersion: "networking.istio.io/v1alpha3",
|
||||||
Kind: "VirtualService",
|
Kind: "VirtualService",
|
||||||
|
|
@ -174,7 +175,7 @@ func TestInitialize(t *testing.T) {
|
||||||
return Config{
|
return Config{
|
||||||
StableService: "echoserver",
|
StableService: "echoserver",
|
||||||
CanaryService: "echoserver-canary",
|
CanaryService: "echoserver-canary",
|
||||||
TrafficConf: []rolloutsv1alpha1.CustomNetworkRef{
|
TrafficConf: []v1beta1.ObjectRef{
|
||||||
{
|
{
|
||||||
APIVersion: "networking.test.io/v1alpha3",
|
APIVersion: "networking.test.io/v1alpha3",
|
||||||
Kind: "VirtualService",
|
Kind: "VirtualService",
|
||||||
|
|
@ -237,7 +238,7 @@ func TestEnsureRoutes(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
getLua func() map[string]string
|
getLua func() map[string]string
|
||||||
getRoutes func() *rolloutsv1alpha1.TrafficRoutingStrategy
|
getRoutes func() *v1beta1.TrafficRoutingStrategy
|
||||||
getUnstructureds func() []*unstructured.Unstructured
|
getUnstructureds func() []*unstructured.Unstructured
|
||||||
getConfig func() Config
|
getConfig func() Config
|
||||||
expectState func() (bool, bool)
|
expectState func() (bool, bool)
|
||||||
|
|
@ -245,9 +246,9 @@ func TestEnsureRoutes(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "test1, do traffic routing for VirtualService and DestinationRule",
|
name: "test1, do traffic routing for VirtualService and DestinationRule",
|
||||||
getRoutes: func() *rolloutsv1alpha1.TrafficRoutingStrategy {
|
getRoutes: func() *v1beta1.TrafficRoutingStrategy {
|
||||||
return &rolloutsv1alpha1.TrafficRoutingStrategy{
|
return &v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(5),
|
Traffic: utilpointer.String("5%"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getUnstructureds: func() []*unstructured.Unstructured {
|
getUnstructureds: func() []*unstructured.Unstructured {
|
||||||
|
|
@ -268,7 +269,7 @@ func TestEnsureRoutes(t *testing.T) {
|
||||||
Key: "rollout-demo",
|
Key: "rollout-demo",
|
||||||
StableService: "echoserver",
|
StableService: "echoserver",
|
||||||
CanaryService: "echoserver-canary",
|
CanaryService: "echoserver-canary",
|
||||||
TrafficConf: []rolloutsv1alpha1.CustomNetworkRef{
|
TrafficConf: []v1beta1.ObjectRef{
|
||||||
{
|
{
|
||||||
APIVersion: "networking.istio.io/v1alpha3",
|
APIVersion: "networking.istio.io/v1alpha3",
|
||||||
Kind: "VirtualService",
|
Kind: "VirtualService",
|
||||||
|
|
@ -317,9 +318,9 @@ func TestEnsureRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test2, do traffic routing but failed to execute lua",
|
name: "test2, do traffic routing but failed to execute lua",
|
||||||
getRoutes: func() *rolloutsv1alpha1.TrafficRoutingStrategy {
|
getRoutes: func() *v1beta1.TrafficRoutingStrategy {
|
||||||
return &rolloutsv1alpha1.TrafficRoutingStrategy{
|
return &v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(5),
|
Traffic: utilpointer.String("5%"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getUnstructureds: func() []*unstructured.Unstructured {
|
getUnstructureds: func() []*unstructured.Unstructured {
|
||||||
|
|
@ -340,7 +341,7 @@ func TestEnsureRoutes(t *testing.T) {
|
||||||
Key: "rollout-demo",
|
Key: "rollout-demo",
|
||||||
StableService: "echoserver",
|
StableService: "echoserver",
|
||||||
CanaryService: "echoserver-canary",
|
CanaryService: "echoserver-canary",
|
||||||
TrafficConf: []rolloutsv1alpha1.CustomNetworkRef{
|
TrafficConf: []v1beta1.ObjectRef{
|
||||||
{
|
{
|
||||||
APIVersion: "networking.istio.io/v1alpha3",
|
APIVersion: "networking.istio.io/v1alpha3",
|
||||||
Kind: "VirtualService",
|
Kind: "VirtualService",
|
||||||
|
|
@ -446,7 +447,7 @@ func TestFinalise(t *testing.T) {
|
||||||
return Config{
|
return Config{
|
||||||
StableService: "echoserver",
|
StableService: "echoserver",
|
||||||
CanaryService: "echoserver-canary",
|
CanaryService: "echoserver-canary",
|
||||||
TrafficConf: []rolloutsv1alpha1.CustomNetworkRef{
|
TrafficConf: []v1beta1.ObjectRef{
|
||||||
{
|
{
|
||||||
APIVersion: "networking.istio.io/v1alpha3",
|
APIVersion: "networking.istio.io/v1alpha3",
|
||||||
Kind: "VirtualService",
|
Kind: "VirtualService",
|
||||||
|
|
@ -482,7 +483,7 @@ func TestFinalise(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestCase struct {
|
type TestCase struct {
|
||||||
Rollout *rolloutsv1beta1.Rollout `json:"rollout,omitempty"`
|
Rollout *v1beta1.Rollout `json:"rollout,omitempty"`
|
||||||
TrafficRouting *rolloutsv1alpha1.TrafficRouting `json:"trafficRouting,omitempty"`
|
TrafficRouting *rolloutsv1alpha1.TrafficRouting `json:"trafficRouting,omitempty"`
|
||||||
Original *unstructured.Unstructured `json:"original,omitempty"`
|
Original *unstructured.Unstructured `json:"original,omitempty"`
|
||||||
Expected []*unstructured.Unstructured `json:"expected,omitempty"`
|
Expected []*unstructured.Unstructured `json:"expected,omitempty"`
|
||||||
|
|
@ -522,8 +523,12 @@ func TestLuaScript(t *testing.T) {
|
||||||
if rollout != nil {
|
if rollout != nil {
|
||||||
steps := rollout.Spec.Strategy.Canary.Steps
|
steps := rollout.Spec.Strategy.Canary.Steps
|
||||||
for i, step := range steps {
|
for i, step := range steps {
|
||||||
weight := step.TrafficRoutingStrategy.Weight
|
var weight *int32
|
||||||
if weight == nil {
|
if step.TrafficRoutingStrategy.Traffic != nil {
|
||||||
|
is := intstr.FromString(*step.TrafficRoutingStrategy.Traffic)
|
||||||
|
weightInt, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
|
||||||
|
weight = utilpointer.Int32(int32(weightInt))
|
||||||
|
} else {
|
||||||
weight = utilpointer.Int32(-1)
|
weight = utilpointer.Int32(-1)
|
||||||
}
|
}
|
||||||
var canaryService string
|
var canaryService string
|
||||||
|
|
@ -563,13 +568,19 @@ func TestLuaScript(t *testing.T) {
|
||||||
var canaryService string
|
var canaryService string
|
||||||
stableService := trafficRouting.Spec.ObjectRef[0].Service
|
stableService := trafficRouting.Spec.ObjectRef[0].Service
|
||||||
canaryService = stableService
|
canaryService = stableService
|
||||||
|
matches := make([]v1beta1.HttpRouteMatch, 0)
|
||||||
|
for _, match := range trafficRouting.Spec.Strategy.Matches {
|
||||||
|
obj := v1beta1.HttpRouteMatch{}
|
||||||
|
obj.Headers = match.Headers
|
||||||
|
matches = append(matches, obj)
|
||||||
|
}
|
||||||
data := &LuaData{
|
data := &LuaData{
|
||||||
Data: Data{
|
Data: Data{
|
||||||
Labels: testCase.Original.GetLabels(),
|
Labels: testCase.Original.GetLabels(),
|
||||||
Annotations: testCase.Original.GetAnnotations(),
|
Annotations: testCase.Original.GetAnnotations(),
|
||||||
Spec: testCase.Original.Object["spec"],
|
Spec: testCase.Original.Object["spec"],
|
||||||
},
|
},
|
||||||
Matches: trafficRouting.Spec.Strategy.Matches,
|
Matches: matches,
|
||||||
CanaryWeight: *weight,
|
CanaryWeight: *weight,
|
||||||
StableWeight: 100 - *weight,
|
StableWeight: 100 - *weight,
|
||||||
CanaryService: canaryService,
|
CanaryService: canaryService,
|
||||||
|
|
@ -615,6 +626,7 @@ func getLuaTestCase(t *testing.T, path string) (*TestCase, error) {
|
||||||
t.Errorf("failed to read file %s", path)
|
t.Errorf("failed to read file %s", path)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
fmt.Println(string(yamlFile))
|
||||||
luaTestCase := &TestCase{}
|
luaTestCase := &TestCase{}
|
||||||
err = yaml.Unmarshal(yamlFile, luaTestCase)
|
err = yaml.Unmarshal(yamlFile, luaTestCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
rolloutv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
"github.com/openkruise/rollouts/api/v1beta1"
|
||||||
"github.com/openkruise/rollouts/pkg/trafficrouting/network"
|
"github.com/openkruise/rollouts/pkg/trafficrouting/network"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/client-go/util/retry"
|
"k8s.io/client-go/util/retry"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
utilpointer "k8s.io/utils/pointer"
|
utilpointer "k8s.io/utils/pointer"
|
||||||
|
|
@ -34,7 +35,7 @@ type Config struct {
|
||||||
Namespace string
|
Namespace string
|
||||||
CanaryService string
|
CanaryService string
|
||||||
StableService string
|
StableService string
|
||||||
TrafficConf *rolloutv1alpha1.GatewayTrafficRouting
|
TrafficConf *v1beta1.GatewayTrafficRouting
|
||||||
}
|
}
|
||||||
|
|
||||||
type gatewayController struct {
|
type gatewayController struct {
|
||||||
|
|
@ -56,8 +57,13 @@ func (r *gatewayController) Initialize(ctx context.Context) error {
|
||||||
return r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: *r.conf.TrafficConf.HTTPRouteName}, route)
|
return r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: *r.conf.TrafficConf.HTTPRouteName}, route)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *gatewayController) EnsureRoutes(ctx context.Context, strategy *rolloutv1alpha1.TrafficRoutingStrategy) (bool, error) {
|
func (r *gatewayController) EnsureRoutes(ctx context.Context, strategy *v1beta1.TrafficRoutingStrategy) (bool, error) {
|
||||||
weight := strategy.Weight
|
var weight *int32
|
||||||
|
if strategy.Traffic != nil {
|
||||||
|
is := intstr.FromString(*strategy.Traffic)
|
||||||
|
weightInt, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
|
||||||
|
weight = utilpointer.Int32(int32(weightInt))
|
||||||
|
}
|
||||||
matches := strategy.Matches
|
matches := strategy.Matches
|
||||||
// headerModifier := strategy.RequestHeaderModifier
|
// headerModifier := strategy.RequestHeaderModifier
|
||||||
var httpRoute gatewayv1alpha2.HTTPRoute
|
var httpRoute gatewayv1alpha2.HTTPRoute
|
||||||
|
|
@ -118,7 +124,7 @@ func (r *gatewayController) Finalise(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *gatewayController) buildDesiredHTTPRoute(rules []gatewayv1alpha2.HTTPRouteRule, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch,
|
func (r *gatewayController) buildDesiredHTTPRoute(rules []gatewayv1alpha2.HTTPRouteRule, weight *int32, matches []v1beta1.HttpRouteMatch,
|
||||||
rh *gatewayv1alpha2.HTTPRequestHeaderFilter) []gatewayv1alpha2.HTTPRouteRule {
|
rh *gatewayv1alpha2.HTTPRequestHeaderFilter) []gatewayv1alpha2.HTTPRouteRule {
|
||||||
var desired []gatewayv1alpha2.HTTPRouteRule
|
var desired []gatewayv1alpha2.HTTPRouteRule
|
||||||
// Only when finalize method parameter weight=-1,
|
// Only when finalize method parameter weight=-1,
|
||||||
|
|
@ -146,7 +152,7 @@ func (r *gatewayController) buildDesiredHTTPRoute(rules []gatewayv1alpha2.HTTPRo
|
||||||
return r.buildCanaryWeightHttpRoutes(rules, weight)
|
return r.buildCanaryWeightHttpRoutes(rules, weight)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *gatewayController) buildCanaryHeaderHttpRoutes(rules []gatewayv1alpha2.HTTPRouteRule, matchs []rolloutv1alpha1.HttpRouteMatch) []gatewayv1alpha2.HTTPRouteRule {
|
func (r *gatewayController) buildCanaryHeaderHttpRoutes(rules []gatewayv1alpha2.HTTPRouteRule, matchs []v1beta1.HttpRouteMatch) []gatewayv1alpha2.HTTPRouteRule {
|
||||||
var desired []gatewayv1alpha2.HTTPRouteRule
|
var desired []gatewayv1alpha2.HTTPRouteRule
|
||||||
var canarys []gatewayv1alpha2.HTTPRouteRule
|
var canarys []gatewayv1alpha2.HTTPRouteRule
|
||||||
for i := range rules {
|
for i := range rules {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
rolloutsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
"github.com/openkruise/rollouts/api/v1beta1"
|
||||||
"github.com/openkruise/rollouts/pkg/util"
|
"github.com/openkruise/rollouts/pkg/util"
|
||||||
utilpointer "k8s.io/utils/pointer"
|
utilpointer "k8s.io/utils/pointer"
|
||||||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||||
|
|
@ -130,7 +130,7 @@ func TestBuildDesiredHTTPRoute(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
getRouteRules func() []gatewayv1alpha2.HTTPRouteRule
|
getRouteRules func() []gatewayv1alpha2.HTTPRouteRule
|
||||||
getRoutes func() (*int32, []rolloutsv1alpha1.HttpRouteMatch)
|
getRoutes func() (*int32, []v1beta1.HttpRouteMatch)
|
||||||
desiredRules func() []gatewayv1alpha2.HTTPRouteRule
|
desiredRules func() []gatewayv1alpha2.HTTPRouteRule
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|
@ -139,9 +139,9 @@ func TestBuildDesiredHTTPRoute(t *testing.T) {
|
||||||
rules := routeDemo.DeepCopy().Spec.Rules
|
rules := routeDemo.DeepCopy().Spec.Rules
|
||||||
return rules
|
return rules
|
||||||
},
|
},
|
||||||
getRoutes: func() (*int32, []rolloutsv1alpha1.HttpRouteMatch) {
|
getRoutes: func() (*int32, []v1beta1.HttpRouteMatch) {
|
||||||
iType := gatewayv1alpha2.HeaderMatchRegularExpression
|
iType := gatewayv1alpha2.HeaderMatchRegularExpression
|
||||||
return nil, []rolloutsv1alpha1.HttpRouteMatch{
|
return nil, []v1beta1.HttpRouteMatch{
|
||||||
// header
|
// header
|
||||||
{
|
{
|
||||||
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
|
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
|
||||||
|
|
@ -360,7 +360,7 @@ func TestBuildDesiredHTTPRoute(t *testing.T) {
|
||||||
}
|
}
|
||||||
return rules
|
return rules
|
||||||
},
|
},
|
||||||
getRoutes: func() (*int32, []rolloutsv1alpha1.HttpRouteMatch) {
|
getRoutes: func() (*int32, []v1beta1.HttpRouteMatch) {
|
||||||
return utilpointer.Int32(20), nil
|
return utilpointer.Int32(20), nil
|
||||||
},
|
},
|
||||||
desiredRules: func() []gatewayv1alpha2.HTTPRouteRule {
|
desiredRules: func() []gatewayv1alpha2.HTTPRouteRule {
|
||||||
|
|
@ -494,7 +494,7 @@ func TestBuildDesiredHTTPRoute(t *testing.T) {
|
||||||
})
|
})
|
||||||
return rules
|
return rules
|
||||||
},
|
},
|
||||||
getRoutes: func() (*int32, []rolloutsv1alpha1.HttpRouteMatch) {
|
getRoutes: func() (*int32, []v1beta1.HttpRouteMatch) {
|
||||||
return utilpointer.Int32(-1), nil
|
return utilpointer.Int32(-1), nil
|
||||||
},
|
},
|
||||||
desiredRules: func() []gatewayv1alpha2.HTTPRouteRule {
|
desiredRules: func() []gatewayv1alpha2.HTTPRouteRule {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
rolloutv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
"github.com/openkruise/rollouts/api/v1beta1"
|
||||||
"github.com/openkruise/rollouts/pkg/trafficrouting/network"
|
"github.com/openkruise/rollouts/pkg/trafficrouting/network"
|
||||||
"github.com/openkruise/rollouts/pkg/util"
|
"github.com/openkruise/rollouts/pkg/util"
|
||||||
"github.com/openkruise/rollouts/pkg/util/configuration"
|
"github.com/openkruise/rollouts/pkg/util/configuration"
|
||||||
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
utilpointer "k8s.io/utils/pointer"
|
utilpointer "k8s.io/utils/pointer"
|
||||||
|
|
@ -56,7 +57,7 @@ type Config struct {
|
||||||
Namespace string
|
Namespace string
|
||||||
CanaryService string
|
CanaryService string
|
||||||
StableService string
|
StableService string
|
||||||
TrafficConf *rolloutv1alpha1.IngressTrafficRouting
|
TrafficConf *v1beta1.IngressTrafficRouting
|
||||||
OwnerRef metav1.OwnerReference
|
OwnerRef metav1.OwnerReference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,8 +83,13 @@ func (r *ingressController) Initialize(ctx context.Context) error {
|
||||||
return r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: r.conf.TrafficConf.Name}, ingress)
|
return r.Get(ctx, types.NamespacedName{Namespace: r.conf.Namespace, Name: r.conf.TrafficConf.Name}, ingress)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ingressController) EnsureRoutes(ctx context.Context, strategy *rolloutv1alpha1.TrafficRoutingStrategy) (bool, error) {
|
func (r *ingressController) EnsureRoutes(ctx context.Context, strategy *v1beta1.TrafficRoutingStrategy) (bool, error) {
|
||||||
weight := strategy.Weight
|
var weight *int32
|
||||||
|
if strategy.Traffic != nil {
|
||||||
|
is := intstr.FromString(*strategy.Traffic)
|
||||||
|
weightInt, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
|
||||||
|
weight = utilpointer.Int32(int32(weightInt))
|
||||||
|
}
|
||||||
matches := strategy.Matches
|
matches := strategy.Matches
|
||||||
headerModifier := strategy.RequestHeaderModifier
|
headerModifier := strategy.RequestHeaderModifier
|
||||||
|
|
||||||
|
|
@ -217,7 +223,7 @@ func defaultCanaryIngressName(name string) string {
|
||||||
return fmt.Sprintf("%s-canary", name)
|
return fmt.Sprintf("%s-canary", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ingressController) executeLuaForCanary(annotations map[string]string, weight *int32, matches []rolloutv1alpha1.HttpRouteMatch,
|
func (r *ingressController) executeLuaForCanary(annotations map[string]string, weight *int32, matches []v1beta1.HttpRouteMatch,
|
||||||
headerModifier *gatewayv1alpha2.HTTPRequestHeaderFilter) (map[string]string, error) {
|
headerModifier *gatewayv1alpha2.HTTPRequestHeaderFilter) (map[string]string, error) {
|
||||||
|
|
||||||
if weight == nil {
|
if weight == nil {
|
||||||
|
|
@ -228,7 +234,7 @@ func (r *ingressController) executeLuaForCanary(annotations map[string]string, w
|
||||||
type LuaData struct {
|
type LuaData struct {
|
||||||
Annotations map[string]string
|
Annotations map[string]string
|
||||||
Weight string
|
Weight string
|
||||||
Matches []rolloutv1alpha1.HttpRouteMatch
|
Matches []v1beta1.HttpRouteMatch
|
||||||
CanaryService string
|
CanaryService string
|
||||||
RequestHeaderModifier *gatewayv1alpha2.HTTPRequestHeaderFilter
|
RequestHeaderModifier *gatewayv1alpha2.HTTPRequestHeaderFilter
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
rolloutsapi "github.com/openkruise/rollouts/api"
|
rolloutsapi "github.com/openkruise/rollouts/api"
|
||||||
rolloutsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
"github.com/openkruise/rollouts/api/v1beta1"
|
||||||
rolloutsv1beta1 "github.com/openkruise/rollouts/api/v1beta1"
|
|
||||||
"github.com/openkruise/rollouts/pkg/util"
|
"github.com/openkruise/rollouts/pkg/util"
|
||||||
"github.com/openkruise/rollouts/pkg/util/configuration"
|
"github.com/openkruise/rollouts/pkg/util/configuration"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
|
@ -279,7 +278,7 @@ func TestInitialize(t *testing.T) {
|
||||||
Key: "rollout-demo",
|
Key: "rollout-demo",
|
||||||
StableService: "echoserver",
|
StableService: "echoserver",
|
||||||
CanaryService: "echoserver-canary",
|
CanaryService: "echoserver-canary",
|
||||||
TrafficConf: &rolloutsv1alpha1.IngressTrafficRouting{
|
TrafficConf: &v1beta1.IngressTrafficRouting{
|
||||||
Name: "echoserver",
|
Name: "echoserver",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -309,7 +308,7 @@ func TestEnsureRoutes(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
getConfigmap func() *corev1.ConfigMap
|
getConfigmap func() *corev1.ConfigMap
|
||||||
getIngress func() []*netv1.Ingress
|
getIngress func() []*netv1.Ingress
|
||||||
getRoutes func() *rolloutsv1beta1.CanaryStep
|
getRoutes func() *v1beta1.CanaryStep
|
||||||
expectIngress func() *netv1.Ingress
|
expectIngress func() *netv1.Ingress
|
||||||
ingressType string
|
ingressType string
|
||||||
}{
|
}{
|
||||||
|
|
@ -329,11 +328,11 @@ func TestEnsureRoutes(t *testing.T) {
|
||||||
canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
||||||
return []*netv1.Ingress{demoIngress.DeepCopy(), canary}
|
return []*netv1.Ingress{demoIngress.DeepCopy(), canary}
|
||||||
},
|
},
|
||||||
getRoutes: func() *rolloutsv1beta1.CanaryStep {
|
getRoutes: func() *v1beta1.CanaryStep {
|
||||||
return &rolloutsv1beta1.CanaryStep{
|
return &v1beta1.CanaryStep{
|
||||||
TrafficRoutingStrategy: rolloutsv1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: nil,
|
Traffic: nil,
|
||||||
Matches: []rolloutsv1alpha1.HttpRouteMatch{
|
Matches: []v1beta1.HttpRouteMatch{
|
||||||
// header
|
// header
|
||||||
{
|
{
|
||||||
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
|
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
|
||||||
|
|
@ -400,10 +399,10 @@ func TestEnsureRoutes(t *testing.T) {
|
||||||
canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
||||||
return []*netv1.Ingress{demoIngress.DeepCopy(), canary}
|
return []*netv1.Ingress{demoIngress.DeepCopy(), canary}
|
||||||
},
|
},
|
||||||
getRoutes: func() *rolloutsv1beta1.CanaryStep {
|
getRoutes: func() *v1beta1.CanaryStep {
|
||||||
return &rolloutsv1beta1.CanaryStep{
|
return &v1beta1.CanaryStep{
|
||||||
TrafficRoutingStrategy: rolloutsv1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(40),
|
Traffic: utilpointer.String("40%"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -435,11 +434,11 @@ func TestEnsureRoutes(t *testing.T) {
|
||||||
canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
||||||
return []*netv1.Ingress{demoIngress.DeepCopy(), canary}
|
return []*netv1.Ingress{demoIngress.DeepCopy(), canary}
|
||||||
},
|
},
|
||||||
getRoutes: func() *rolloutsv1beta1.CanaryStep {
|
getRoutes: func() *v1beta1.CanaryStep {
|
||||||
iType := gatewayv1alpha2.HeaderMatchRegularExpression
|
iType := gatewayv1alpha2.HeaderMatchRegularExpression
|
||||||
return &rolloutsv1beta1.CanaryStep{
|
return &v1beta1.CanaryStep{
|
||||||
TrafficRoutingStrategy: rolloutsv1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Matches: []rolloutsv1alpha1.HttpRouteMatch{
|
Matches: []v1beta1.HttpRouteMatch{
|
||||||
// header
|
// header
|
||||||
{
|
{
|
||||||
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
|
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
|
||||||
|
|
@ -483,10 +482,10 @@ func TestEnsureRoutes(t *testing.T) {
|
||||||
canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
canary.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary"
|
||||||
return []*netv1.Ingress{demoIngress.DeepCopy(), canary}
|
return []*netv1.Ingress{demoIngress.DeepCopy(), canary}
|
||||||
},
|
},
|
||||||
getRoutes: func() *rolloutsv1beta1.CanaryStep {
|
getRoutes: func() *v1beta1.CanaryStep {
|
||||||
return &rolloutsv1beta1.CanaryStep{
|
return &v1beta1.CanaryStep{
|
||||||
TrafficRoutingStrategy: rolloutsv1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{
|
||||||
Matches: []rolloutsv1alpha1.HttpRouteMatch{
|
Matches: []v1beta1.HttpRouteMatch{
|
||||||
// header
|
// header
|
||||||
{
|
{
|
||||||
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
|
Headers: []gatewayv1alpha2.HTTPHeaderMatch{
|
||||||
|
|
@ -526,7 +525,7 @@ func TestEnsureRoutes(t *testing.T) {
|
||||||
Key: "rollout-demo",
|
Key: "rollout-demo",
|
||||||
StableService: "echoserver",
|
StableService: "echoserver",
|
||||||
CanaryService: "echoserver-canary",
|
CanaryService: "echoserver-canary",
|
||||||
TrafficConf: &rolloutsv1alpha1.IngressTrafficRouting{
|
TrafficConf: &v1beta1.IngressTrafficRouting{
|
||||||
Name: "echoserver",
|
Name: "echoserver",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -596,7 +595,7 @@ func TestFinalise(t *testing.T) {
|
||||||
Key: "rollout-demo",
|
Key: "rollout-demo",
|
||||||
StableService: "echoserver",
|
StableService: "echoserver",
|
||||||
CanaryService: "echoserver-canary",
|
CanaryService: "echoserver-canary",
|
||||||
TrafficConf: &rolloutsv1alpha1.IngressTrafficRouting{
|
TrafficConf: &v1beta1.IngressTrafficRouting{
|
||||||
Name: "echoserver",
|
Name: "echoserver",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ package network
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
rolloutv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
"github.com/openkruise/rollouts/api/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NetworkProvider common function across all TrafficRouting implementation
|
// NetworkProvider common function across all TrafficRouting implementation
|
||||||
|
|
@ -33,7 +33,7 @@ type NetworkProvider interface {
|
||||||
// 1. check if canary has been set desired weight.
|
// 1. check if canary has been set desired weight.
|
||||||
// 2. If not, set canary desired weight
|
// 2. If not, set canary desired weight
|
||||||
// When the first set weight is returned false, mainly to give the provider some time to process, only when again ensure, will return true
|
// When the first set weight is returned false, mainly to give the provider some time to process, only when again ensure, will return true
|
||||||
EnsureRoutes(ctx context.Context, strategy *rolloutv1alpha1.TrafficRoutingStrategy) (bool, error)
|
EnsureRoutes(ctx context.Context, strategy *v1beta1.TrafficRoutingStrategy) (bool, error)
|
||||||
// Finalise will do some cleanup work after the canary rollout complete, such as delete canary ingress.
|
// Finalise will do some cleanup work after the canary rollout complete, such as delete canary ingress.
|
||||||
// Finalise is called with a 3-second delay after completing the canary.
|
// Finalise is called with a 3-second delay after completing the canary.
|
||||||
Finalise(ctx context.Context) error
|
Finalise(ctx context.Context) error
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ type Workload struct {
|
||||||
|
|
||||||
// ControllerFinderFunc is a function type that maps a pod to a list of
|
// ControllerFinderFunc is a function type that maps a pod to a list of
|
||||||
// controllers and their scale.
|
// controllers and their scale.
|
||||||
type ControllerFinderFunc func(namespace string, ref *rolloutv1beta1.WorkloadRef) (*Workload, error)
|
type ControllerFinderFunc func(namespace string, ref *rolloutv1beta1.ObjectRef) (*Workload, error)
|
||||||
|
|
||||||
type ControllerFinder struct {
|
type ControllerFinder struct {
|
||||||
client.Client
|
client.Client
|
||||||
|
|
@ -86,29 +86,17 @@ func NewControllerFinder(c client.Client) *ControllerFinder {
|
||||||
// +kubebuilder:rbac:groups=apps,resources=replicasets/status,verbs=get;update;patch
|
// +kubebuilder:rbac:groups=apps,resources=replicasets/status,verbs=get;update;patch
|
||||||
|
|
||||||
func (r *ControllerFinder) GetWorkloadForRef(rollout *rolloutv1beta1.Rollout) (*Workload, error) {
|
func (r *ControllerFinder) GetWorkloadForRef(rollout *rolloutv1beta1.Rollout) (*Workload, error) {
|
||||||
workloadRef := rollout.Spec.ObjectRef.WorkloadRef
|
workloadRef := rollout.Spec.WorkloadRef
|
||||||
if workloadRef == nil {
|
if rollout.Spec.Strategy.Canary.EnableExtraWorkloadForCanary {
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch strings.ToLower(rollout.Annotations[rolloutv1beta1.RolloutStyleAnnotation]) {
|
|
||||||
case strings.ToLower(string(rolloutv1alpha1.PartitionRollingStyle)):
|
|
||||||
for _, finder := range r.partitionStyleFinders() {
|
|
||||||
workload, err := finder(rollout.Namespace, workloadRef)
|
|
||||||
if workload != nil || err != nil {
|
|
||||||
return workload, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case strings.ToLower(string(rolloutv1alpha1.CanaryRollingStyle)):
|
|
||||||
for _, finder := range r.canaryStyleFinders() {
|
|
||||||
workload, err := finder(rollout.Namespace, workloadRef)
|
|
||||||
if workload != nil || err != nil {
|
|
||||||
return workload, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
for _, finder := range append(r.canaryStyleFinders(), r.partitionStyleFinders()...) {
|
for _, finder := range append(r.canaryStyleFinders(), r.partitionStyleFinders()...) {
|
||||||
workload, err := finder(rollout.Namespace, workloadRef)
|
workload, err := finder(rollout.Namespace, &workloadRef)
|
||||||
|
if workload != nil || err != nil {
|
||||||
|
return workload, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, finder := range r.partitionStyleFinders() {
|
||||||
|
workload, err := finder(rollout.Namespace, &workloadRef)
|
||||||
if workload != nil || err != nil {
|
if workload != nil || err != nil {
|
||||||
return workload, err
|
return workload, err
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +126,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// getKruiseCloneSet returns the kruise cloneSet referenced by the provided controllerRef.
|
// getKruiseCloneSet returns the kruise cloneSet referenced by the provided controllerRef.
|
||||||
func (r *ControllerFinder) getKruiseCloneSet(namespace string, ref *rolloutv1beta1.WorkloadRef) (*Workload, error) {
|
func (r *ControllerFinder) getKruiseCloneSet(namespace string, ref *rolloutv1beta1.ObjectRef) (*Workload, error) {
|
||||||
// This error is irreversible, so there is no need to return error
|
// This error is irreversible, so there is no need to return error
|
||||||
ok, _ := verifyGroupKind(ref, ControllerKruiseKindCS.Kind, []string{ControllerKruiseKindCS.Group})
|
ok, _ := verifyGroupKind(ref, ControllerKruiseKindCS.Kind, []string{ControllerKruiseKindCS.Group})
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -180,7 +168,7 @@ func (r *ControllerFinder) getKruiseCloneSet(namespace string, ref *rolloutv1bet
|
||||||
return workload, nil
|
return workload, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ControllerFinder) getKruiseDaemonSet(namespace string, ref *rolloutv1beta1.WorkloadRef) (*Workload, error) {
|
func (r *ControllerFinder) getKruiseDaemonSet(namespace string, ref *rolloutv1beta1.ObjectRef) (*Workload, error) {
|
||||||
// This error is irreversible, so there is no need to return error
|
// This error is irreversible, so there is no need to return error
|
||||||
ok, _ := verifyGroupKind(ref, ControllerKruiseKindDS.Kind, []string{ControllerKruiseKindDS.Group})
|
ok, _ := verifyGroupKind(ref, ControllerKruiseKindDS.Kind, []string{ControllerKruiseKindDS.Group})
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -224,7 +212,7 @@ func (r *ControllerFinder) getKruiseDaemonSet(namespace string, ref *rolloutv1be
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPartitionStyleDeployment returns the Advanced Deployment referenced by the provided controllerRef.
|
// getPartitionStyleDeployment returns the Advanced Deployment referenced by the provided controllerRef.
|
||||||
func (r *ControllerFinder) getAdvancedDeployment(namespace string, ref *rolloutv1beta1.WorkloadRef) (*Workload, error) {
|
func (r *ControllerFinder) getAdvancedDeployment(namespace string, ref *rolloutv1beta1.ObjectRef) (*Workload, error) {
|
||||||
// This error is irreversible, so there is no need to return error
|
// This error is irreversible, so there is no need to return error
|
||||||
ok, _ := verifyGroupKind(ref, ControllerKindDep.Kind, []string{ControllerKindDep.Group})
|
ok, _ := verifyGroupKind(ref, ControllerKindDep.Kind, []string{ControllerKindDep.Group})
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -278,7 +266,7 @@ func (r *ControllerFinder) getAdvancedDeployment(namespace string, ref *rolloutv
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDeployment returns the k8s native deployment referenced by the provided controllerRef.
|
// getDeployment returns the k8s native deployment referenced by the provided controllerRef.
|
||||||
func (r *ControllerFinder) getDeployment(namespace string, ref *rolloutv1beta1.WorkloadRef) (*Workload, error) {
|
func (r *ControllerFinder) getDeployment(namespace string, ref *rolloutv1beta1.ObjectRef) (*Workload, error) {
|
||||||
// This error is irreversible, so there is no need to return error
|
// This error is irreversible, so there is no need to return error
|
||||||
ok, _ := verifyGroupKind(ref, ControllerKindDep.Kind, []string{ControllerKindDep.Group})
|
ok, _ := verifyGroupKind(ref, ControllerKindDep.Kind, []string{ControllerKindDep.Group})
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -335,7 +323,7 @@ func (r *ControllerFinder) getDeployment(namespace string, ref *rolloutv1beta1.W
|
||||||
return workload, err
|
return workload, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ControllerFinder) getStatefulSetLikeWorkload(namespace string, ref *rolloutv1beta1.WorkloadRef) (*Workload, error) {
|
func (r *ControllerFinder) getStatefulSetLikeWorkload(namespace string, ref *rolloutv1beta1.ObjectRef) (*Workload, error) {
|
||||||
if ref == nil {
|
if ref == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
@ -446,7 +434,7 @@ func (r *ControllerFinder) getDeploymentStableRs(obj *apps.Deployment) (*apps.Re
|
||||||
return rss[0], nil
|
return rss[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyGroupKind(ref *rolloutv1beta1.WorkloadRef, expectedKind string, expectedGroups []string) (bool, error) {
|
func verifyGroupKind(ref *rolloutv1beta1.ObjectRef, expectedKind string, expectedGroups []string) (bool, error) {
|
||||||
gv, err := schema.ParseGroupVersion(ref.APIVersion)
|
gv, err := schema.ParseGroupVersion(ref.APIVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ func IsRollbackInBatchPolicy(rollout *rolloutv1beta1.Rollout, labels map[string]
|
||||||
if len(rollout.Spec.Strategy.Canary.TrafficRoutings) > 0 {
|
if len(rollout.Spec.Strategy.Canary.TrafficRoutings) > 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
workloadRef := rollout.Spec.ObjectRef.WorkloadRef
|
workloadRef := rollout.Spec.WorkloadRef
|
||||||
//currently, only CloneSet, StatefulSet support this policy
|
//currently, only CloneSet, StatefulSet support this policy
|
||||||
if workloadRef.Kind == ControllerKindSts.Kind ||
|
if workloadRef.Kind == ControllerKindSts.Kind ||
|
||||||
workloadRef.Kind == ControllerKruiseKindCS.Kind ||
|
workloadRef.Kind == ControllerKruiseKindCS.Kind ||
|
||||||
|
|
@ -131,7 +131,7 @@ func DiscoverGVK(gvk schema.GroupVersionKind) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGVKFrom(workloadRef *rolloutv1beta1.WorkloadRef) schema.GroupVersionKind {
|
func GetGVKFrom(workloadRef *rolloutv1beta1.ObjectRef) schema.GroupVersionKind {
|
||||||
if workloadRef == nil {
|
if workloadRef == nil {
|
||||||
return schema.GroupVersionKind{}
|
return schema.GroupVersionKind{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
|
|
||||||
appsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
appsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
||||||
appsv1beta1 "github.com/openkruise/rollouts/api/v1beta1"
|
appsv1beta1 "github.com/openkruise/rollouts/api/v1beta1"
|
||||||
|
|
@ -131,15 +130,15 @@ func (h *RolloutCreateUpdateHandler) validateRolloutUpdate(oldObj, newObj *appsv
|
||||||
switch latestObject.Status.Phase {
|
switch latestObject.Status.Phase {
|
||||||
// The workloadRef and TrafficRouting are not allowed to be modified in the Progressing, Terminating state
|
// The workloadRef and TrafficRouting are not allowed to be modified in the Progressing, Terminating state
|
||||||
case appsv1beta1.RolloutPhaseProgressing, appsv1beta1.RolloutPhaseTerminating:
|
case appsv1beta1.RolloutPhaseProgressing, appsv1beta1.RolloutPhaseTerminating:
|
||||||
if !reflect.DeepEqual(oldObj.Spec.ObjectRef, newObj.Spec.ObjectRef) {
|
if !reflect.DeepEqual(oldObj.Spec.WorkloadRef, newObj.Spec.WorkloadRef) {
|
||||||
return field.ErrorList{field.Forbidden(field.NewPath("Spec.ObjectRef"), "Rollout 'ObjectRef' field is immutable")}
|
return field.ErrorList{field.Forbidden(field.NewPath("Spec.ObjectRef"), "Rollout 'ObjectRef' field is immutable")}
|
||||||
}
|
}
|
||||||
// canary strategy
|
// canary strategy
|
||||||
if !reflect.DeepEqual(oldObj.Spec.Strategy.Canary.TrafficRoutings, newObj.Spec.Strategy.Canary.TrafficRoutings) {
|
if !reflect.DeepEqual(oldObj.Spec.Strategy.Canary.TrafficRoutings, newObj.Spec.Strategy.Canary.TrafficRoutings) {
|
||||||
return field.ErrorList{field.Forbidden(field.NewPath("Spec.Strategy.Canary.TrafficRoutings"), "Rollout 'Strategy.Canary.TrafficRoutings' field is immutable")}
|
return field.ErrorList{field.Forbidden(field.NewPath("Spec.Strategy.Canary.TrafficRoutings"), "Rollout 'Strategy.Canary.TrafficRoutings' field is immutable")}
|
||||||
}
|
}
|
||||||
if !strings.EqualFold(oldObj.Annotations[appsv1beta1.RolloutStyleAnnotation], newObj.Annotations[appsv1beta1.RolloutStyleAnnotation]) {
|
if oldObj.Spec.Strategy.Canary.EnableExtraWorkloadForCanary != newObj.Spec.Strategy.Canary.EnableExtraWorkloadForCanary {
|
||||||
return field.ErrorList{field.Forbidden(field.NewPath("Metadata.Annotation"), "Rollout 'Rolling-Style' annotation is immutable")}
|
return field.ErrorList{field.Forbidden(field.NewPath("Spec.Strategy.Canary"), "Rollout enableExtraWorkloadForCanary is immutable")}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,7 +170,7 @@ func (h *RolloutCreateUpdateHandler) validateRolloutConflict(rollout *appsv1beta
|
||||||
}
|
}
|
||||||
for i := range rolloutList.Items {
|
for i := range rolloutList.Items {
|
||||||
r := &rolloutList.Items[i]
|
r := &rolloutList.Items[i]
|
||||||
if r.Name == rollout.Name || !IsSameWorkloadRefGVKName(r.Spec.ObjectRef.WorkloadRef, rollout.Spec.ObjectRef.WorkloadRef) {
|
if r.Name == rollout.Name || !IsSameWorkloadRefGVKName(&r.Spec.WorkloadRef, &rollout.Spec.WorkloadRef) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return field.ErrorList{field.Invalid(path, rollout.Name,
|
return field.ErrorList{field.Invalid(path, rollout.Name,
|
||||||
|
|
@ -181,39 +180,32 @@ func (h *RolloutCreateUpdateHandler) validateRolloutConflict(rollout *appsv1beta
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRolloutSpec(rollout *appsv1beta1.Rollout, fldPath *field.Path) field.ErrorList {
|
func validateRolloutSpec(rollout *appsv1beta1.Rollout, fldPath *field.Path) field.ErrorList {
|
||||||
errList := validateRolloutSpecObjectRef(&rollout.Spec.ObjectRef, fldPath.Child("ObjectRef"))
|
errList := validateRolloutSpecObjectRef(&rollout.Spec.WorkloadRef, fldPath.Child("ObjectRef"))
|
||||||
errList = append(errList, validateRolloutRollingStyle(rollout, field.NewPath("RollingStyle"))...)
|
errList = append(errList, validateRolloutRollingStyle(rollout, field.NewPath("RollingStyle"))...)
|
||||||
errList = append(errList, validateRolloutSpecStrategy(&rollout.Spec.Strategy, fldPath.Child("Strategy"))...)
|
errList = append(errList, validateRolloutSpecStrategy(&rollout.Spec.Strategy, fldPath.Child("Strategy"))...)
|
||||||
return errList
|
return errList
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRolloutRollingStyle(rollout *appsv1beta1.Rollout, fldPath *field.Path) field.ErrorList {
|
func validateRolloutRollingStyle(rollout *appsv1beta1.Rollout, fldPath *field.Path) field.ErrorList {
|
||||||
switch strings.ToLower(rollout.Annotations[appsv1beta1.RolloutStyleAnnotation]) {
|
workloadRef := rollout.Spec.WorkloadRef
|
||||||
case "", strings.ToLower(string(appsv1alpha1.CanaryRollingStyle)), strings.ToLower(string(appsv1alpha1.PartitionRollingStyle)):
|
if workloadRef.Kind == util.ControllerKindDep.Kind {
|
||||||
default:
|
|
||||||
return field.ErrorList{field.Invalid(fldPath, rollout.Annotations[appsv1beta1.RolloutStyleAnnotation],
|
|
||||||
"Rolling style must be 'Canary', 'Partition' or empty")}
|
|
||||||
}
|
|
||||||
|
|
||||||
workloadRef := rollout.Spec.ObjectRef.WorkloadRef
|
|
||||||
if workloadRef == nil || workloadRef.Kind == util.ControllerKindDep.Kind {
|
|
||||||
return nil // Deployment support all rolling styles, no need to validate.
|
return nil // Deployment support all rolling styles, no need to validate.
|
||||||
}
|
}
|
||||||
if strings.EqualFold(rollout.Annotations[appsv1beta1.RolloutStyleAnnotation], string(appsv1alpha1.CanaryRollingStyle)) {
|
if rollout.Spec.Strategy.Canary.EnableExtraWorkloadForCanary {
|
||||||
return field.ErrorList{field.Invalid(fldPath, rollout.Annotations[appsv1beta1.RolloutStyleAnnotation],
|
return field.ErrorList{field.Invalid(fldPath, rollout.Spec.Strategy.Canary.EnableExtraWorkloadForCanary,
|
||||||
"Only Deployment support canary rolling style")}
|
"Only Deployment can set enableExtraWorkloadForCanary=true")}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRolloutSpecObjectRef(objectRef *appsv1beta1.ObjectRef, fldPath *field.Path) field.ErrorList {
|
func validateRolloutSpecObjectRef(workloadRef *appsv1beta1.ObjectRef, fldPath *field.Path) field.ErrorList {
|
||||||
if objectRef.WorkloadRef == nil {
|
if workloadRef == nil {
|
||||||
return field.ErrorList{field.Invalid(fldPath.Child("WorkloadRef"), objectRef.WorkloadRef, "WorkloadRef is required")}
|
return field.ErrorList{field.Invalid(fldPath.Child("WorkloadRef"), workloadRef, "WorkloadRef is required")}
|
||||||
}
|
}
|
||||||
|
|
||||||
gvk := schema.FromAPIVersionAndKind(objectRef.WorkloadRef.APIVersion, objectRef.WorkloadRef.Kind)
|
gvk := schema.FromAPIVersionAndKind(workloadRef.APIVersion, workloadRef.Kind)
|
||||||
if !util.IsSupportedWorkload(gvk) {
|
if !util.IsSupportedWorkload(gvk) {
|
||||||
return field.ErrorList{field.Invalid(fldPath.Child("WorkloadRef"), objectRef.WorkloadRef, "WorkloadRef kind is not supported")}
|
return field.ErrorList{field.Invalid(fldPath.Child("WorkloadRef"), workloadRef, "WorkloadRef kind is not supported")}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +229,7 @@ func validateRolloutSpecCanaryStrategy(canary *appsv1beta1.CanaryStrategy, fldPa
|
||||||
return errList
|
return errList
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRolloutSpecCanaryTraffic(traffic appsv1alpha1.TrafficRoutingRef, fldPath *field.Path) field.ErrorList {
|
func validateRolloutSpecCanaryTraffic(traffic appsv1beta1.TrafficRoutingRef, fldPath *field.Path) field.ErrorList {
|
||||||
errList := field.ErrorList{}
|
errList := field.ErrorList{}
|
||||||
if len(traffic.Service) == 0 {
|
if len(traffic.Service) == 0 {
|
||||||
errList = append(errList, field.Invalid(fldPath.Child("Service"), traffic.Service, "TrafficRouting.Service cannot be empty"))
|
errList = append(errList, field.Invalid(fldPath.Child("Service"), traffic.Service, "TrafficRouting.Service cannot be empty"))
|
||||||
|
|
@ -269,40 +261,38 @@ func validateRolloutSpecCanarySteps(steps []appsv1beta1.CanaryStep, fldPath *fie
|
||||||
|
|
||||||
for i := range steps {
|
for i := range steps {
|
||||||
s := &steps[i]
|
s := &steps[i]
|
||||||
if s.Weight == nil && s.Replicas == nil {
|
if s.Replicas == nil {
|
||||||
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("steps"), steps, `weight and replicas cannot be empty at the same time`)}
|
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("steps"), steps, `replicas cannot be empty`)}
|
||||||
}
|
}
|
||||||
if s.Replicas != nil {
|
canaryReplicas, err := intstr.GetScaledValueFromIntOrPercent(s.Replicas, 100, true)
|
||||||
canaryReplicas, err := intstr.GetScaledValueFromIntOrPercent(s.Replicas, 100, true)
|
if err != nil ||
|
||||||
if err != nil ||
|
canaryReplicas <= 0 ||
|
||||||
canaryReplicas <= 0 ||
|
(canaryReplicas > 100 && s.Replicas.Type == intstr.String) {
|
||||||
(canaryReplicas > 100 && s.Replicas.Type == intstr.String) {
|
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("Replicas"),
|
||||||
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("Replicas"),
|
s.Replicas, `replicas must be positive number, or a percentage with "0%" < canaryReplicas <= "100%"`)}
|
||||||
s.Replicas, `replicas must be positive number, or a percentage with "0%" < canaryReplicas <= "100%"`)}
|
}
|
||||||
}
|
if !isTraffic {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.Traffic == nil {
|
||||||
|
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("steps"), steps, `traffic cannot be empty`)}
|
||||||
|
}
|
||||||
|
is := intstr.FromString(*s.Traffic)
|
||||||
|
weight, err := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
|
||||||
|
if err != nil || weight <= 0 || weight > 100 {
|
||||||
|
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("steps"), steps, `traffic must be percentage with "0%" < traffic <= "100%"`)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 1; i < stepCount; i++ {
|
for i := 1; i < stepCount; i++ {
|
||||||
prev := &steps[i-1]
|
prev := &steps[i-1]
|
||||||
curr := &steps[i]
|
curr := &steps[i]
|
||||||
if isTraffic && curr.Weight != nil && prev.Weight != nil && *curr.Weight < *prev.Weight {
|
|
||||||
return field.ErrorList{field.Invalid(fldPath.Child("Weight"), steps, `Steps.Weight must be a non decreasing sequence`)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if they are comparable, then compare them
|
// if they are comparable, then compare them
|
||||||
if IsPercentageCanaryReplicasType(prev.Replicas) != IsPercentageCanaryReplicasType(curr.Replicas) {
|
if IsPercentageCanaryReplicasType(prev.Replicas) != IsPercentageCanaryReplicasType(curr.Replicas) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
prevCanaryReplicas, _ := intstr.GetScaledValueFromIntOrPercent(prev.Replicas, 100, true)
|
prevCanaryReplicas, _ := intstr.GetScaledValueFromIntOrPercent(prev.Replicas, 100, true)
|
||||||
currCanaryReplicas, _ := intstr.GetScaledValueFromIntOrPercent(curr.Replicas, 100, true)
|
currCanaryReplicas, _ := intstr.GetScaledValueFromIntOrPercent(curr.Replicas, 100, true)
|
||||||
if prev.Replicas == nil {
|
|
||||||
prevCanaryReplicas = int(*prev.Weight)
|
|
||||||
}
|
|
||||||
if curr.Replicas == nil {
|
|
||||||
currCanaryReplicas = int(*curr.Weight)
|
|
||||||
}
|
|
||||||
if currCanaryReplicas < prevCanaryReplicas {
|
if currCanaryReplicas < prevCanaryReplicas {
|
||||||
return field.ErrorList{field.Invalid(fldPath.Child("CanaryReplicas"), steps, `Steps.CanaryReplicas must be a non decreasing sequence`)}
|
return field.ErrorList{field.Invalid(fldPath.Child("CanaryReplicas"), steps, `Steps.CanaryReplicas must be a non decreasing sequence`)}
|
||||||
}
|
}
|
||||||
|
|
@ -315,7 +305,7 @@ func IsPercentageCanaryReplicasType(replicas *intstr.IntOrString) bool {
|
||||||
return replicas == nil || replicas.Type == intstr.String
|
return replicas == nil || replicas.Type == intstr.String
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsSameWorkloadRefGVKName(a, b *appsv1beta1.WorkloadRef) bool {
|
func IsSameWorkloadRefGVKName(a, b *appsv1beta1.ObjectRef) bool {
|
||||||
if a == nil || b == nil {
|
if a == nil || b == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
rolloutapi "github.com/openkruise/rollouts/api"
|
rolloutapi "github.com/openkruise/rollouts/api"
|
||||||
appsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
|
||||||
appsv1beta1 "github.com/openkruise/rollouts/api/v1beta1"
|
appsv1beta1 "github.com/openkruise/rollouts/api/v1beta1"
|
||||||
apps "k8s.io/api/apps/v1"
|
apps "k8s.io/api/apps/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
@ -47,56 +46,46 @@ var (
|
||||||
Annotations: map[string]string{},
|
Annotations: map[string]string{},
|
||||||
},
|
},
|
||||||
Spec: appsv1beta1.RolloutSpec{
|
Spec: appsv1beta1.RolloutSpec{
|
||||||
ObjectRef: appsv1beta1.ObjectRef{
|
WorkloadRef: appsv1beta1.ObjectRef{
|
||||||
WorkloadRef: &appsv1beta1.WorkloadRef{
|
APIVersion: apps.SchemeGroupVersion.String(),
|
||||||
APIVersion: apps.SchemeGroupVersion.String(),
|
Kind: "Deployment",
|
||||||
Kind: "Deployment",
|
Name: "deployment-demo",
|
||||||
Name: "deployment-demo",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Strategy: appsv1beta1.RolloutStrategy{
|
Strategy: appsv1beta1.RolloutStrategy{
|
||||||
Canary: &appsv1beta1.CanaryStrategy{
|
Canary: &appsv1beta1.CanaryStrategy{
|
||||||
Steps: []appsv1beta1.CanaryStep{
|
Steps: []appsv1beta1.CanaryStep{
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(10),
|
Traffic: utilpointer.String("10%"),
|
||||||
},
|
},
|
||||||
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(1)},
|
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(1)},
|
||||||
Pause: appsv1beta1.RolloutPause{},
|
Pause: appsv1beta1.RolloutPause{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(10),
|
Traffic: utilpointer.String("10%"),
|
||||||
},
|
},
|
||||||
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(3)},
|
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(3)},
|
||||||
Pause: appsv1beta1.RolloutPause{Duration: utilpointer.Int32(1 * 24 * 60 * 60)},
|
Pause: appsv1beta1.RolloutPause{Duration: utilpointer.Int32(1 * 24 * 60 * 60)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(30),
|
Traffic: utilpointer.String("30%"),
|
||||||
},
|
},
|
||||||
Pause: appsv1beta1.RolloutPause{Duration: utilpointer.Int32(7 * 24 * 60 * 60)},
|
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(10)},
|
||||||
|
Pause: appsv1beta1.RolloutPause{Duration: utilpointer.Int32(7 * 24 * 60 * 60)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
|
TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{
|
||||||
Weight: utilpointer.Int32(100),
|
Traffic: utilpointer.String("100%"),
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
|
|
||||||
Weight: utilpointer.Int32(101),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
|
|
||||||
Weight: utilpointer.Int32(200),
|
|
||||||
},
|
},
|
||||||
|
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(20)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TrafficRoutings: []appsv1alpha1.TrafficRoutingRef{
|
TrafficRoutings: []appsv1beta1.TrafficRoutingRef{
|
||||||
{
|
{
|
||||||
Service: "service-demo",
|
Service: "service-demo",
|
||||||
Ingress: &appsv1alpha1.IngressTrafficRouting{
|
Ingress: &appsv1beta1.IngressTrafficRouting{
|
||||||
ClassType: "nginx",
|
ClassType: "nginx",
|
||||||
Name: "ingress-nginx-demo",
|
Name: "ingress-nginx-demo",
|
||||||
},
|
},
|
||||||
|
|
@ -129,21 +118,13 @@ func TestRolloutValidateCreate(t *testing.T) {
|
||||||
Name: "Normal case",
|
Name: "Normal case",
|
||||||
Succeed: true,
|
Succeed: true,
|
||||||
GetObject: func() []client.Object {
|
GetObject: func() []client.Object {
|
||||||
return []client.Object{rollout.DeepCopy()}
|
obj := rollout.DeepCopy()
|
||||||
|
return []client.Object{obj}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
/***********************************************************
|
/***********************************************************
|
||||||
The following cases may lead to controller panic
|
The following cases may lead to controller panic
|
||||||
**********************************************************/
|
**********************************************************/
|
||||||
{
|
|
||||||
Name: "WorkloadRef is nil",
|
|
||||||
Succeed: false,
|
|
||||||
GetObject: func() []client.Object {
|
|
||||||
object := rollout.DeepCopy()
|
|
||||||
object.Spec.ObjectRef.WorkloadRef = nil
|
|
||||||
return []client.Object{object}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "Canary is nil",
|
Name: "Canary is nil",
|
||||||
Succeed: false,
|
Succeed: false,
|
||||||
|
|
@ -188,29 +169,21 @@ func TestRolloutValidateCreate(t *testing.T) {
|
||||||
Succeed: true,
|
Succeed: true,
|
||||||
GetObject: func() []client.Object {
|
GetObject: func() []client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Spec.ObjectRef.WorkloadRef = &appsv1beta1.WorkloadRef{
|
object.Spec.WorkloadRef = appsv1beta1.ObjectRef{
|
||||||
APIVersion: "apps/v1",
|
APIVersion: "apps/v1",
|
||||||
Kind: "StatefulSet",
|
Kind: "StatefulSet",
|
||||||
Name: "whatever",
|
Name: "whatever",
|
||||||
}
|
}
|
||||||
|
|
||||||
return []client.Object{object}
|
return []client.Object{object}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Steps.Weight is a decreasing sequence",
|
Name: "Steps.Traffic is a decreasing sequence",
|
||||||
Succeed: false,
|
Succeed: false,
|
||||||
GetObject: func() []client.Object {
|
GetObject: func() []client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Spec.Strategy.Canary.Steps[2].Weight = utilpointer.Int32Ptr(5)
|
object.Spec.Strategy.Canary.Steps[2].Traffic = utilpointer.String("%5")
|
||||||
return []client.Object{object}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Steps.Replicas is a decreasing sequence",
|
|
||||||
Succeed: false,
|
|
||||||
GetObject: func() []client.Object {
|
|
||||||
object := rollout.DeepCopy()
|
|
||||||
object.Spec.Strategy.Canary.Steps[1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "50%"}
|
|
||||||
return []client.Object{object}
|
return []client.Object{object}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -242,20 +215,20 @@ func TestRolloutValidateCreate(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Steps.Weight is illegal value, 0",
|
Name: "Steps.Traffic is illegal value, 0",
|
||||||
Succeed: false,
|
Succeed: false,
|
||||||
GetObject: func() []client.Object {
|
GetObject: func() []client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Spec.Strategy.Canary.Steps[1].Weight = utilpointer.Int32Ptr(0)
|
object.Spec.Strategy.Canary.Steps[1].Traffic = utilpointer.String("0%")
|
||||||
return []client.Object{object}
|
return []client.Object{object}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Steps.Weight is illegal value, 101",
|
Name: "Steps.Traffic is illegal value, 101",
|
||||||
Succeed: false,
|
Succeed: false,
|
||||||
GetObject: func() []client.Object {
|
GetObject: func() []client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Spec.Strategy.Canary.Steps[1].Weight = utilpointer.Int32Ptr(101)
|
object.Spec.Strategy.Canary.Steps[1].Traffic = utilpointer.String("101%")
|
||||||
return []client.Object{object}
|
return []client.Object{object}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -264,9 +237,7 @@ func TestRolloutValidateCreate(t *testing.T) {
|
||||||
Succeed: true,
|
Succeed: true,
|
||||||
GetObject: func() []client.Object {
|
GetObject: func() []client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Annotations = map[string]string{
|
object.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true
|
||||||
appsv1beta1.RolloutStyleAnnotation: "Canary",
|
|
||||||
}
|
|
||||||
return []client.Object{object}
|
return []client.Object{object}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -275,20 +246,6 @@ func TestRolloutValidateCreate(t *testing.T) {
|
||||||
Succeed: true,
|
Succeed: true,
|
||||||
GetObject: func() []client.Object {
|
GetObject: func() []client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Annotations = map[string]string{
|
|
||||||
appsv1beta1.RolloutStyleAnnotation: "Partition",
|
|
||||||
}
|
|
||||||
return []client.Object{object}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Wrong rolling style",
|
|
||||||
Succeed: false,
|
|
||||||
GetObject: func() []client.Object {
|
|
||||||
object := rollout.DeepCopy()
|
|
||||||
object.Annotations = map[string]string{
|
|
||||||
appsv1beta1.RolloutStyleAnnotation: "Other",
|
|
||||||
}
|
|
||||||
return []client.Object{object}
|
return []client.Object{object}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -297,21 +254,19 @@ func TestRolloutValidateCreate(t *testing.T) {
|
||||||
Succeed: false,
|
Succeed: false,
|
||||||
GetObject: func() []client.Object {
|
GetObject: func() []client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Annotations = map[string]string{
|
object.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true
|
||||||
appsv1beta1.RolloutStyleAnnotation: "Canary",
|
object.Spec.WorkloadRef.APIVersion = "apps.kruise.io/v1alpha1"
|
||||||
}
|
object.Spec.WorkloadRef.Kind = "CloneSet"
|
||||||
object.Spec.ObjectRef.WorkloadRef.APIVersion = "apps.kruise.io/v1alpha1"
|
|
||||||
object.Spec.ObjectRef.WorkloadRef.Kind = "CloneSet"
|
|
||||||
return []client.Object{object}
|
return []client.Object{object}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
//{
|
//{
|
||||||
// Name: "The last Steps.Weight is not 100",
|
// Name: "The last Steps.Traffic is not 100",
|
||||||
// Succeed: false,
|
// Succeed: false,
|
||||||
// GetObject: func() []client.Object {
|
// GetObject: func() []client.Object {
|
||||||
// object := rollout.DeepCopy()
|
// object := rollout.DeepCopy()
|
||||||
// n := len(object.Spec.Strategy.Canary.Steps)
|
// n := len(object.Spec.Strategy.Canary.Steps)
|
||||||
// object.Spec.Strategy.Canary.Steps[n-1].Weight = 80
|
// object.Spec.Strategy.Canary.Steps[n-1].Traffic = 80
|
||||||
// return []client.Object{object}
|
// return []client.Object{object}
|
||||||
// },
|
// },
|
||||||
//},
|
//},
|
||||||
|
|
@ -326,15 +281,15 @@ func TestRolloutValidateCreate(t *testing.T) {
|
||||||
|
|
||||||
object1 := rollout.DeepCopy()
|
object1 := rollout.DeepCopy()
|
||||||
object1.Name = "object-1"
|
object1.Name = "object-1"
|
||||||
object1.Spec.ObjectRef.WorkloadRef.Name = "another"
|
object1.Spec.WorkloadRef.Name = "another"
|
||||||
|
|
||||||
object2 := rollout.DeepCopy()
|
object2 := rollout.DeepCopy()
|
||||||
object2.Name = "object-2"
|
object2.Name = "object-2"
|
||||||
object2.Spec.ObjectRef.WorkloadRef.APIVersion = "another"
|
object2.Spec.WorkloadRef.APIVersion = "another"
|
||||||
|
|
||||||
object3 := rollout.DeepCopy()
|
object3 := rollout.DeepCopy()
|
||||||
object3.Name = "object-3"
|
object3.Name = "object-3"
|
||||||
object3.Spec.ObjectRef.WorkloadRef.Kind = "another"
|
object3.Spec.WorkloadRef.Kind = "another"
|
||||||
|
|
||||||
return []client.Object{
|
return []client.Object{
|
||||||
object, object1, object2, object3,
|
object, object1, object2, object3,
|
||||||
|
|
@ -349,15 +304,15 @@ func TestRolloutValidateCreate(t *testing.T) {
|
||||||
|
|
||||||
object1 := rollout.DeepCopy()
|
object1 := rollout.DeepCopy()
|
||||||
object1.Name = "object-1"
|
object1.Name = "object-1"
|
||||||
object1.Spec.ObjectRef.WorkloadRef.Name = "another"
|
object1.Spec.WorkloadRef.Name = "another"
|
||||||
|
|
||||||
object2 := rollout.DeepCopy()
|
object2 := rollout.DeepCopy()
|
||||||
object2.Name = "object-2"
|
object2.Name = "object-2"
|
||||||
object2.Spec.ObjectRef.WorkloadRef.APIVersion = "another"
|
object2.Spec.WorkloadRef.APIVersion = "another"
|
||||||
|
|
||||||
object3 := rollout.DeepCopy()
|
object3 := rollout.DeepCopy()
|
||||||
object3.Name = "object-3"
|
object3.Name = "object-3"
|
||||||
object3.Spec.ObjectRef.WorkloadRef.Kind = "another"
|
object3.Spec.WorkloadRef.Kind = "another"
|
||||||
|
|
||||||
object4 := rollout.DeepCopy()
|
object4 := rollout.DeepCopy()
|
||||||
object4.Name = "object-4"
|
object4.Name = "object-4"
|
||||||
|
|
@ -400,7 +355,7 @@ func TestRolloutValidateUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
GetNewObject: func() client.Object {
|
GetNewObject: func() client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Spec.Strategy.Canary.Steps[0].Weight = utilpointer.Int32Ptr(5)
|
object.Spec.Strategy.Canary.Steps[0].Traffic = utilpointer.String("5%")
|
||||||
return object
|
return object
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -430,7 +385,7 @@ func TestRolloutValidateUpdate(t *testing.T) {
|
||||||
GetNewObject: func() client.Object {
|
GetNewObject: func() client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Status.Phase = appsv1beta1.RolloutPhaseProgressing
|
object.Status.Phase = appsv1beta1.RolloutPhaseProgressing
|
||||||
object.Spec.Strategy.Canary.Steps[0].Weight = utilpointer.Int32Ptr(5)
|
object.Spec.Strategy.Canary.Steps[0].Traffic = utilpointer.String("5%")
|
||||||
return object
|
return object
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -439,14 +394,13 @@ func TestRolloutValidateUpdate(t *testing.T) {
|
||||||
Succeed: false,
|
Succeed: false,
|
||||||
GetOldObject: func() client.Object {
|
GetOldObject: func() client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Annotations[appsv1beta1.RolloutStyleAnnotation] = "Partition"
|
|
||||||
object.Status.Phase = appsv1beta1.RolloutPhaseProgressing
|
object.Status.Phase = appsv1beta1.RolloutPhaseProgressing
|
||||||
return object
|
return object
|
||||||
},
|
},
|
||||||
GetNewObject: func() client.Object {
|
GetNewObject: func() client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Status.Phase = appsv1beta1.RolloutPhaseProgressing
|
object.Status.Phase = appsv1beta1.RolloutPhaseProgressing
|
||||||
object.Annotations[appsv1beta1.RolloutStyleAnnotation] = "Canary"
|
object.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true
|
||||||
return object
|
return object
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -476,7 +430,7 @@ func TestRolloutValidateUpdate(t *testing.T) {
|
||||||
GetNewObject: func() client.Object {
|
GetNewObject: func() client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Status.Phase = appsv1beta1.RolloutPhaseInitial
|
object.Status.Phase = appsv1beta1.RolloutPhaseInitial
|
||||||
object.Spec.Strategy.Canary.Steps[0].Weight = utilpointer.Int32Ptr(5)
|
object.Spec.Strategy.Canary.Steps[0].Traffic = utilpointer.String("5%")
|
||||||
return object
|
return object
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -491,7 +445,7 @@ func TestRolloutValidateUpdate(t *testing.T) {
|
||||||
GetNewObject: func() client.Object {
|
GetNewObject: func() client.Object {
|
||||||
object := rollout.DeepCopy()
|
object := rollout.DeepCopy()
|
||||||
object.Status.Phase = appsv1beta1.RolloutPhaseHealthy
|
object.Status.Phase = appsv1beta1.RolloutPhaseHealthy
|
||||||
object.Spec.Strategy.Canary.Steps[0].Weight = utilpointer.Int32Ptr(5)
|
object.Spec.Strategy.Canary.Steps[0].Traffic = utilpointer.String("5%")
|
||||||
return object
|
return object
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -107,15 +107,6 @@ func validateV1alpha1RolloutRollingStyle(rollout *appsv1alpha1.Rollout, fldPath
|
||||||
return field.ErrorList{field.Invalid(fldPath, rollout.Annotations[appsv1alpha1.RolloutStyleAnnotation],
|
return field.ErrorList{field.Invalid(fldPath, rollout.Annotations[appsv1alpha1.RolloutStyleAnnotation],
|
||||||
"Rolling style must be 'Canary', 'Partition' or empty")}
|
"Rolling style must be 'Canary', 'Partition' or empty")}
|
||||||
}
|
}
|
||||||
|
|
||||||
workloadRef := rollout.Spec.ObjectRef.WorkloadRef
|
|
||||||
if workloadRef == nil || workloadRef.Kind == util.ControllerKindDep.Kind {
|
|
||||||
return nil // Deployment support all rolling styles, no need to validate.
|
|
||||||
}
|
|
||||||
if strings.EqualFold(rollout.Annotations[appsv1alpha1.RolloutStyleAnnotation], string(appsv1alpha1.CanaryRollingStyle)) {
|
|
||||||
return field.ErrorList{field.Invalid(fldPath, rollout.Annotations[appsv1alpha1.RolloutStyleAnnotation],
|
|
||||||
"Only Deployment support canary rolling style")}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,11 +136,35 @@ func validateV1alpha1RolloutSpecCanaryStrategy(canary *appsv1alpha1.CanaryStrate
|
||||||
errList = append(errList, field.Invalid(fldPath, canary.TrafficRoutings, "Rollout currently only support single TrafficRouting."))
|
errList = append(errList, field.Invalid(fldPath, canary.TrafficRoutings, "Rollout currently only support single TrafficRouting."))
|
||||||
}
|
}
|
||||||
for _, traffic := range canary.TrafficRoutings {
|
for _, traffic := range canary.TrafficRoutings {
|
||||||
errList = append(errList, validateRolloutSpecCanaryTraffic(traffic, fldPath.Child("TrafficRouting"))...)
|
errList = append(errList, validateV1alpha1RolloutSpecCanaryTraffic(traffic, fldPath.Child("TrafficRouting"))...)
|
||||||
}
|
}
|
||||||
return errList
|
return errList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateV1alpha1RolloutSpecCanaryTraffic(traffic appsv1alpha1.TrafficRoutingRef, fldPath *field.Path) field.ErrorList {
|
||||||
|
errList := field.ErrorList{}
|
||||||
|
if len(traffic.Service) == 0 {
|
||||||
|
errList = append(errList, field.Invalid(fldPath.Child("Service"), traffic.Service, "TrafficRouting.Service cannot be empty"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if traffic.Gateway == nil && traffic.Ingress == nil && traffic.CustomNetworkRefs == nil {
|
||||||
|
errList = append(errList, field.Invalid(fldPath.Child("TrafficRoutings"), traffic.Ingress, "TrafficRoutings are not set"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if traffic.Ingress != nil {
|
||||||
|
if traffic.Ingress.Name == "" {
|
||||||
|
errList = append(errList, field.Invalid(fldPath.Child("Ingress"), traffic.Ingress, "TrafficRouting.Ingress.Ingress cannot be empty"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if traffic.Gateway != nil {
|
||||||
|
if traffic.Gateway.HTTPRouteName == nil || *traffic.Gateway.HTTPRouteName == "" {
|
||||||
|
errList = append(errList, field.Invalid(fldPath.Child("Gateway"), traffic.Gateway, "TrafficRouting.Gateway must set the name of HTTPRoute or HTTPsRoute"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errList
|
||||||
|
}
|
||||||
|
|
||||||
func validateV1alpha1RolloutSpecCanarySteps(steps []appsv1alpha1.CanaryStep, fldPath *field.Path, isTraffic bool) field.ErrorList {
|
func validateV1alpha1RolloutSpecCanarySteps(steps []appsv1alpha1.CanaryStep, fldPath *field.Path, isTraffic bool) field.ErrorList {
|
||||||
stepCount := len(steps)
|
stepCount := len(steps)
|
||||||
if stepCount == 0 {
|
if stepCount == 0 {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GateFunc func() (enabled bool)
|
type GateFunc func() (enabled bool)
|
||||||
|
|
@ -90,11 +91,12 @@ func SetupWithManager(mgr manager.Manager) error {
|
||||||
server.Register(path, &webhook.Admission{Handler: handler})
|
server.Register(path, &webhook.Admission{Handler: handler})
|
||||||
klog.V(3).Infof("Registered webhook handler %s", path)
|
klog.V(3).Infof("Registered webhook handler %s", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := initialize(context.TODO(), mgr.GetConfig())
|
err := initialize(context.TODO(), mgr.GetConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// register conversion webhook
|
||||||
|
server.Register("/convert", &conversion.Webhook{})
|
||||||
klog.Infof("webhook init done")
|
klog.Infof("webhook init done")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
|
|
||||||
webhookutil "github.com/openkruise/rollouts/pkg/webhook/util"
|
webhookutil "github.com/openkruise/rollouts/pkg/webhook/util"
|
||||||
"github.com/openkruise/rollouts/pkg/webhook/util/configuration"
|
"github.com/openkruise/rollouts/pkg/webhook/util/configuration"
|
||||||
|
"github.com/openkruise/rollouts/pkg/webhook/util/crd"
|
||||||
"github.com/openkruise/rollouts/pkg/webhook/util/generator"
|
"github.com/openkruise/rollouts/pkg/webhook/util/generator"
|
||||||
"github.com/openkruise/rollouts/pkg/webhook/util/writer"
|
"github.com/openkruise/rollouts/pkg/webhook/util/writer"
|
||||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||||
|
|
@ -266,6 +267,10 @@ func (c *Controller) sync() error {
|
||||||
return fmt.Errorf("failed to ensure configuration: %v", err)
|
return fmt.Errorf("failed to ensure configuration: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := crd.Ensure(c.crdClient, c.crdLister, certs.CACert); err != nil {
|
||||||
|
return fmt.Errorf("failed to ensure crd: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
onceInit.Do(func() {
|
onceInit.Do(func() {
|
||||||
close(uninit)
|
close(uninit)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Kruise 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 crd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
rolloutapi "github.com/openkruise/rollouts/api"
|
||||||
|
webhookutil "github.com/openkruise/rollouts/pkg/webhook/util"
|
||||||
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
|
apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||||
|
apiextensionslisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
scheme = runtime.NewScheme()
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
utilruntime.Must(rolloutapi.AddToScheme(scheme))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ensure(client apiextensionsclientset.Interface, lister apiextensionslisters.CustomResourceDefinitionLister, caBundle []byte) error {
|
||||||
|
crdList, err := lister.List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list crds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookConfig := apiextensionsv1.WebhookClientConfig{
|
||||||
|
CABundle: caBundle,
|
||||||
|
}
|
||||||
|
path := "/convert"
|
||||||
|
if host := webhookutil.GetHost(); len(host) > 0 {
|
||||||
|
url := fmt.Sprintf("https://%s:%d%s", host, webhookutil.GetPort(), path)
|
||||||
|
webhookConfig.URL = &url
|
||||||
|
} else {
|
||||||
|
var port int32 = 443
|
||||||
|
webhookConfig.Service = &apiextensionsv1.ServiceReference{
|
||||||
|
Namespace: webhookutil.GetNamespace(),
|
||||||
|
Name: webhookutil.GetServiceName(),
|
||||||
|
Port: &port,
|
||||||
|
Path: &path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, crd := range crdList {
|
||||||
|
if len(crd.Spec.Versions) == 0 || crd.Spec.Conversion == nil || crd.Spec.Conversion.Strategy != apiextensionsv1.WebhookConverter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !scheme.Recognizes(schema.GroupVersionKind{Group: crd.Spec.Group, Version: crd.Spec.Versions[0].Name, Kind: crd.Spec.Names.Kind}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if crd.Spec.Conversion.Webhook == nil || !reflect.DeepEqual(crd.Spec.Conversion.Webhook.ClientConfig, webhookConfig) {
|
||||||
|
newCRD := crd.DeepCopy()
|
||||||
|
newCRD.Spec.Conversion.Webhook = &apiextensionsv1.WebhookConversion{
|
||||||
|
ClientConfig: webhookConfig.DeepCopy(),
|
||||||
|
ConversionReviewVersions: []string{"v1", "v1beta1"},
|
||||||
|
}
|
||||||
|
if _, err := client.ApiextensionsV1().CustomResourceDefinitions().Update(context.TODO(), newCRD, metav1.UpdateOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("failed to update CRD %s: %v", newCRD.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -422,14 +422,14 @@ func (h *WorkloadHandler) fetchMatchedRollout(obj client.Object) (*appsv1beta1.R
|
||||||
}
|
}
|
||||||
for i := range rolloutList.Items {
|
for i := range rolloutList.Items {
|
||||||
rollout := &rolloutList.Items[i]
|
rollout := &rolloutList.Items[i]
|
||||||
if !rollout.DeletionTimestamp.IsZero() || rollout.Spec.ObjectRef.WorkloadRef == nil {
|
if !rollout.DeletionTimestamp.IsZero() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if rollout.Status.Phase == appsv1beta1.RolloutPhaseDisabled {
|
if rollout.Status.Phase == appsv1beta1.RolloutPhaseDisabled {
|
||||||
klog.Infof("Disabled rollout(%s/%s) fetched when fetching matched rollout", rollout.Namespace, rollout.Name)
|
klog.Infof("Disabled rollout(%s/%s) fetched when fetching matched rollout", rollout.Namespace, rollout.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ref := rollout.Spec.ObjectRef.WorkloadRef
|
ref := rollout.Spec.WorkloadRef
|
||||||
gv, err := schema.ParseGroupVersion(ref.APIVersion)
|
gv, err := schema.ParseGroupVersion(ref.APIVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Warningf("ParseGroupVersion rollout(%s/%s) ref failed: %s", rollout.Namespace, rollout.Name, err.Error())
|
klog.Warningf("ParseGroupVersion rollout(%s/%s) ref failed: %s", rollout.Namespace, rollout.Name, err.Error())
|
||||||
|
|
|
||||||
|
|
@ -274,12 +274,10 @@ var (
|
||||||
Labels: map[string]string{},
|
Labels: map[string]string{},
|
||||||
},
|
},
|
||||||
Spec: appsv1beta1.RolloutSpec{
|
Spec: appsv1beta1.RolloutSpec{
|
||||||
ObjectRef: appsv1beta1.ObjectRef{
|
WorkloadRef: appsv1beta1.ObjectRef{
|
||||||
WorkloadRef: &appsv1beta1.WorkloadRef{
|
APIVersion: "apps/v1",
|
||||||
APIVersion: "apps/v1",
|
Kind: "Deployment",
|
||||||
Kind: "Deployment",
|
Name: "echoserver",
|
||||||
Name: "echoserver",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Strategy: appsv1beta1.RolloutStrategy{
|
Strategy: appsv1beta1.RolloutStrategy{
|
||||||
Canary: &appsv1beta1.CanaryStrategy{},
|
Canary: &appsv1beta1.CanaryStrategy{},
|
||||||
|
|
@ -345,7 +343,7 @@ func TestHandlerDeployment(t *testing.T) {
|
||||||
},
|
},
|
||||||
getRollout: func() *appsv1beta1.Rollout {
|
getRollout: func() *appsv1beta1.Rollout {
|
||||||
obj := rolloutDemo.DeepCopy()
|
obj := rolloutDemo.DeepCopy()
|
||||||
obj.Spec.ObjectRef.WorkloadRef = &appsv1beta1.WorkloadRef{
|
obj.Spec.WorkloadRef = appsv1beta1.ObjectRef{
|
||||||
APIVersion: "apps/v1",
|
APIVersion: "apps/v1",
|
||||||
Kind: "Deployment",
|
Kind: "Deployment",
|
||||||
Name: "other",
|
Name: "other",
|
||||||
|
|
@ -377,10 +375,10 @@ func TestHandlerDeployment(t *testing.T) {
|
||||||
getRollout: func() *appsv1beta1.Rollout {
|
getRollout: func() *appsv1beta1.Rollout {
|
||||||
demo := rolloutDemo.DeepCopy()
|
demo := rolloutDemo.DeepCopy()
|
||||||
demo.Spec.Strategy.Canary = &appsv1beta1.CanaryStrategy{
|
demo.Spec.Strategy.Canary = &appsv1beta1.CanaryStrategy{
|
||||||
TrafficRoutings: []appsv1alpha1.TrafficRoutingRef{
|
TrafficRoutings: []appsv1beta1.TrafficRoutingRef{
|
||||||
{
|
{
|
||||||
Service: "echoserver",
|
Service: "echoserver",
|
||||||
Ingress: &appsv1alpha1.IngressTrafficRouting{
|
Ingress: &appsv1beta1.IngressTrafficRouting{
|
||||||
Name: "echoserver",
|
Name: "echoserver",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -582,7 +580,7 @@ func TestHandlerCloneSet(t *testing.T) {
|
||||||
},
|
},
|
||||||
getRollout: func() *appsv1beta1.Rollout {
|
getRollout: func() *appsv1beta1.Rollout {
|
||||||
obj := rolloutDemo.DeepCopy()
|
obj := rolloutDemo.DeepCopy()
|
||||||
obj.Spec.ObjectRef.WorkloadRef = &appsv1beta1.WorkloadRef{
|
obj.Spec.WorkloadRef = appsv1beta1.ObjectRef{
|
||||||
APIVersion: "apps.kruise.io/v1alpha1",
|
APIVersion: "apps.kruise.io/v1alpha1",
|
||||||
Kind: "CloneSet",
|
Kind: "CloneSet",
|
||||||
Name: "echoserver",
|
Name: "echoserver",
|
||||||
|
|
@ -646,7 +644,7 @@ func TestHandlerDaemonSet(t *testing.T) {
|
||||||
},
|
},
|
||||||
getRollout: func() *appsv1beta1.Rollout {
|
getRollout: func() *appsv1beta1.Rollout {
|
||||||
obj := rolloutDemo.DeepCopy()
|
obj := rolloutDemo.DeepCopy()
|
||||||
obj.Spec.ObjectRef.WorkloadRef = &appsv1beta1.WorkloadRef{
|
obj.Spec.WorkloadRef = appsv1beta1.ObjectRef{
|
||||||
APIVersion: "apps.kruise.io/v1alpha1",
|
APIVersion: "apps.kruise.io/v1alpha1",
|
||||||
Kind: "DaemonSet",
|
Kind: "DaemonSet",
|
||||||
Name: "echoserver",
|
Name: "echoserver",
|
||||||
|
|
@ -710,7 +708,7 @@ func TestHandleStatefulSet(t *testing.T) {
|
||||||
},
|
},
|
||||||
getRollout: func() *appsv1beta1.Rollout {
|
getRollout: func() *appsv1beta1.Rollout {
|
||||||
obj := rolloutDemo.DeepCopy()
|
obj := rolloutDemo.DeepCopy()
|
||||||
obj.Spec.ObjectRef.WorkloadRef = &appsv1beta1.WorkloadRef{
|
obj.Spec.WorkloadRef = appsv1beta1.ObjectRef{
|
||||||
APIVersion: "apps.kruise.io/v1beta1",
|
APIVersion: "apps.kruise.io/v1beta1",
|
||||||
Kind: "StatefulSet",
|
Kind: "StatefulSet",
|
||||||
Name: "echoserver",
|
Name: "echoserver",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue