add restriction for traffic configuration of partition-style step (#225)
Signed-off-by: yunbo <yunbo10124scut@gmail.com> Co-authored-by: yunbo <yunbo10124scut@gmail.com>
This commit is contained in:
parent
16a3f0acc1
commit
78273c2998
|
@ -18,6 +18,7 @@ package validating
|
|||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
@ -47,7 +48,27 @@ type RolloutCreateUpdateHandler struct {
|
|||
Decoder *admission.Decoder
|
||||
}
|
||||
|
||||
var _ admission.Handler = &RolloutCreateUpdateHandler{}
|
||||
var (
|
||||
_ admission.Handler = &RolloutCreateUpdateHandler{}
|
||||
// PartitionReplicasLimitWithTraffic represents the maximum percentage of replicas
|
||||
// allowed for a step of partition-style release, if traffic/matches specified.
|
||||
// If a step is configured with a number of replicas exceeding this percentage, the traffic strategy for that step
|
||||
// must not be specified. If this rule is violated, the Rollout webhook will block the creation or modification of the Rollout.
|
||||
// The default limit is set to 50%.
|
||||
// Here is why we set this limit for partition style release:
|
||||
// In rollback and continuous scenarios, usually we expect the Rollout to route all traffic to the stable version first.
|
||||
// However, if the stable version's pods are relatively few (less than 1-PartitionReplicasLimitWithTraffic), this might overload the stable version's pods.
|
||||
PartitionReplicasLimitWithTraffic = 50
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.IntVar(&PartitionReplicasLimitWithTraffic, "partition-percent-limit", 50, "represents the maximum percentage of replicas allowed for a step of partition-style release, if traffic/matches specified.")
|
||||
}
|
||||
|
||||
// record upper level information about the rollout
|
||||
type validateContext struct {
|
||||
style string
|
||||
}
|
||||
|
||||
// Handle handles admission requests.
|
||||
func (h *RolloutCreateUpdateHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
|
@ -155,7 +176,7 @@ func (h *RolloutCreateUpdateHandler) validateRolloutUpdate(oldObj, newObj *appsv
|
|||
}
|
||||
|
||||
func (h *RolloutCreateUpdateHandler) validateRollout(rollout *appsv1beta1.Rollout) field.ErrorList {
|
||||
errList := validateRolloutSpec(rollout, field.NewPath("Spec"))
|
||||
errList := validateRolloutSpec(GetContextFromv1beta1Rollout(rollout), rollout, field.NewPath("Spec"))
|
||||
errList = append(errList, h.validateRolloutConflict(rollout, field.NewPath("Conflict Checker"))...)
|
||||
return errList
|
||||
}
|
||||
|
@ -178,9 +199,9 @@ func (h *RolloutCreateUpdateHandler) validateRolloutConflict(rollout *appsv1beta
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateRolloutSpec(rollout *appsv1beta1.Rollout, fldPath *field.Path) field.ErrorList {
|
||||
func validateRolloutSpec(c *validateContext, rollout *appsv1beta1.Rollout, fldPath *field.Path) field.ErrorList {
|
||||
errList := validateRolloutSpecObjectRef(&rollout.Spec.WorkloadRef, fldPath.Child("ObjectRef"))
|
||||
errList = append(errList, validateRolloutSpecStrategy(&rollout.Spec.Strategy, fldPath.Child("Strategy"))...)
|
||||
errList = append(errList, validateRolloutSpecStrategy(c, &rollout.Spec.Strategy, fldPath.Child("Strategy"))...)
|
||||
return errList
|
||||
}
|
||||
|
||||
|
@ -196,7 +217,7 @@ func validateRolloutSpecObjectRef(workloadRef *appsv1beta1.ObjectRef, fldPath *f
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateRolloutSpecStrategy(strategy *appsv1beta1.RolloutStrategy, fldPath *field.Path) field.ErrorList {
|
||||
func validateRolloutSpecStrategy(c *validateContext, strategy *appsv1beta1.RolloutStrategy, fldPath *field.Path) field.ErrorList {
|
||||
if strategy.Canary == nil && strategy.BlueGreen == nil {
|
||||
return field.ErrorList{field.Invalid(fldPath, nil, "Canary and BlueGreen cannot both be empty")}
|
||||
}
|
||||
|
@ -204,25 +225,13 @@ func validateRolloutSpecStrategy(strategy *appsv1beta1.RolloutStrategy, fldPath
|
|||
return field.ErrorList{field.Invalid(fldPath, nil, "Canary and BlueGreen cannot both be set")}
|
||||
}
|
||||
if strategy.BlueGreen != nil {
|
||||
return validateRolloutSpecBlueGreenStrategy(strategy.BlueGreen, fldPath.Child("BlueGreen"))
|
||||
return validateRolloutSpecBlueGreenStrategy(c, strategy.BlueGreen, fldPath.Child("BlueGreen"))
|
||||
}
|
||||
return validateRolloutSpecCanaryStrategy(strategy.Canary, fldPath.Child("Canary"))
|
||||
return validateRolloutSpecCanaryStrategy(c, strategy.Canary, fldPath.Child("Canary"))
|
||||
}
|
||||
|
||||
type TrafficRule string
|
||||
|
||||
const (
|
||||
TrafficRuleCanary TrafficRule = "Canary"
|
||||
TrafficRuleBlueGreen TrafficRule = "BlueGreen"
|
||||
NoTraffic TrafficRule = "NoTraffic"
|
||||
)
|
||||
|
||||
func validateRolloutSpecCanaryStrategy(canary *appsv1beta1.CanaryStrategy, fldPath *field.Path) field.ErrorList {
|
||||
trafficRule := NoTraffic
|
||||
if len(canary.TrafficRoutings) > 0 {
|
||||
trafficRule = TrafficRuleCanary
|
||||
}
|
||||
errList := validateRolloutSpecCanarySteps(canary.Steps, fldPath.Child("Steps"), trafficRule)
|
||||
func validateRolloutSpecCanaryStrategy(c *validateContext, canary *appsv1beta1.CanaryStrategy, fldPath *field.Path) field.ErrorList {
|
||||
errList := validateRolloutSpecCanarySteps(c, canary.Steps, fldPath.Child("Steps"))
|
||||
if len(canary.TrafficRoutings) > 1 {
|
||||
errList = append(errList, field.Invalid(fldPath, canary.TrafficRoutings, "Rollout currently only support single TrafficRouting."))
|
||||
}
|
||||
|
@ -232,12 +241,8 @@ func validateRolloutSpecCanaryStrategy(canary *appsv1beta1.CanaryStrategy, fldPa
|
|||
return errList
|
||||
}
|
||||
|
||||
func validateRolloutSpecBlueGreenStrategy(blueGreen *appsv1beta1.BlueGreenStrategy, fldPath *field.Path) field.ErrorList {
|
||||
trafficRule := NoTraffic
|
||||
if len(blueGreen.TrafficRoutings) > 0 {
|
||||
trafficRule = TrafficRuleBlueGreen
|
||||
}
|
||||
errList := validateRolloutSpecCanarySteps(blueGreen.Steps, fldPath.Child("Steps"), trafficRule)
|
||||
func validateRolloutSpecBlueGreenStrategy(c *validateContext, blueGreen *appsv1beta1.BlueGreenStrategy, fldPath *field.Path) field.ErrorList {
|
||||
errList := validateRolloutSpecCanarySteps(c, blueGreen.Steps, fldPath.Child("Steps"))
|
||||
if len(blueGreen.TrafficRoutings) > 1 {
|
||||
errList = append(errList, field.Invalid(fldPath, blueGreen.TrafficRoutings, "Rollout currently only support single TrafficRouting."))
|
||||
}
|
||||
|
@ -275,7 +280,7 @@ func validateRolloutSpecCanaryTraffic(traffic appsv1beta1.TrafficRoutingRef, fld
|
|||
return errList
|
||||
}
|
||||
|
||||
func validateRolloutSpecCanarySteps(steps []appsv1beta1.CanaryStep, fldPath *field.Path, trafficRule TrafficRule) field.ErrorList {
|
||||
func validateRolloutSpecCanarySteps(c *validateContext, steps []appsv1beta1.CanaryStep, fldPath *field.Path) field.ErrorList {
|
||||
stepCount := len(steps)
|
||||
if stepCount == 0 {
|
||||
return field.ErrorList{field.Invalid(fldPath, steps, "The number of Canary.Steps cannot be empty")}
|
||||
|
@ -293,13 +298,24 @@ func validateRolloutSpecCanarySteps(steps []appsv1beta1.CanaryStep, fldPath *fie
|
|||
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("Replicas"),
|
||||
s.Replicas, `replicas must be positive number, or a percentage with "0%" < canaryReplicas <= "100%"`)}
|
||||
}
|
||||
if trafficRule == NoTraffic || s.Traffic == nil {
|
||||
// no traffic strategy is configured for current step
|
||||
if s.Traffic == nil && len(s.Matches) == 0 {
|
||||
continue
|
||||
}
|
||||
// replicas is percentage
|
||||
if c.style == string(appsv1beta1.PartitionRollingStyle) && IsPercentageCanaryReplicasType(s.Replicas) {
|
||||
currCanaryReplicas, _ := intstr.GetScaledValueFromIntOrPercent(s.Replicas, 100, true)
|
||||
if currCanaryReplicas > PartitionReplicasLimitWithTraffic {
|
||||
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("steps"), steps, `For partition style rollout: step[x].replicas must not greater than partition-percent-limit if traffic specified`)}
|
||||
}
|
||||
}
|
||||
if s.Traffic == nil {
|
||||
continue
|
||||
}
|
||||
is := intstr.FromString(*s.Traffic)
|
||||
weight, err := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
|
||||
switch trafficRule {
|
||||
case TrafficRuleBlueGreen:
|
||||
switch c.style {
|
||||
case string(appsv1beta1.BlueGreenRollingStyle):
|
||||
// traffic "0%" is allowed in blueGreen strategy
|
||||
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%" in blueGreen strategy`)}
|
||||
|
@ -355,3 +371,14 @@ func (h *RolloutCreateUpdateHandler) InjectDecoder(d *admission.Decoder) error {
|
|||
h.Decoder = d
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetContextFromv1beta1Rollout(rollout *appsv1beta1.Rollout) *validateContext {
|
||||
if rollout.Spec.Strategy.Canary == nil && rollout.Spec.Strategy.BlueGreen == nil {
|
||||
return nil
|
||||
}
|
||||
style := rollout.Spec.Strategy.GetRollingStyle()
|
||||
if appsv1beta1.IsRealPartition(rollout) {
|
||||
style = appsv1beta1.PartitionRollingStyle
|
||||
}
|
||||
return &validateContext{style: string(style)}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
rolloutapi "github.com/openkruise/rollouts/api"
|
||||
appsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
||||
appsv1beta1 "github.com/openkruise/rollouts/api/v1beta1"
|
||||
apps "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -102,6 +103,63 @@ var (
|
|||
},
|
||||
},
|
||||
}
|
||||
rolloutV1alpha1 = appsv1alpha1.Rollout{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: appsv1alpha1.SchemeGroupVersion.String(),
|
||||
Kind: "Rollout",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "rollout-demo",
|
||||
Namespace: "namespace-unit-test",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: appsv1alpha1.RolloutSpec{
|
||||
ObjectRef: appsv1alpha1.ObjectRef{
|
||||
WorkloadRef: &appsv1alpha1.WorkloadRef{
|
||||
APIVersion: apps.SchemeGroupVersion.String(),
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-demo",
|
||||
},
|
||||
},
|
||||
Strategy: appsv1alpha1.RolloutStrategy{
|
||||
Canary: &appsv1alpha1.CanaryStrategy{
|
||||
Steps: []appsv1alpha1.CanaryStep{
|
||||
{
|
||||
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
|
||||
Weight: utilpointer.Int32(10),
|
||||
},
|
||||
Pause: appsv1alpha1.RolloutPause{},
|
||||
},
|
||||
{
|
||||
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
|
||||
Weight: utilpointer.Int32(30),
|
||||
},
|
||||
Pause: appsv1alpha1.RolloutPause{Duration: utilpointer.Int32(1 * 24 * 60 * 60)},
|
||||
},
|
||||
{
|
||||
TrafficRoutingStrategy: appsv1alpha1.TrafficRoutingStrategy{
|
||||
Weight: utilpointer.Int32(100),
|
||||
},
|
||||
},
|
||||
},
|
||||
TrafficRoutings: []appsv1alpha1.TrafficRoutingRef{
|
||||
{
|
||||
Service: "service-demo",
|
||||
Ingress: &appsv1alpha1.IngressTrafficRouting{
|
||||
ClassType: "nginx",
|
||||
Name: "ingress-nginx-demo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: appsv1alpha1.RolloutStatus{
|
||||
CanaryStatus: &appsv1alpha1.CanaryStatus{
|
||||
CurrentStepState: appsv1alpha1.CanaryStepStateCompleted,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -262,6 +320,74 @@ func TestRolloutValidateCreate(t *testing.T) {
|
|||
return []client.Object{object}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test with replicasLimitWithTraffic - 1",
|
||||
Succeed: true,
|
||||
GetObject: func() []client.Object {
|
||||
object := rollout.DeepCopy()
|
||||
n := len(object.Spec.Strategy.Canary.Steps)
|
||||
object.Spec.Strategy.Canary.Steps[n-1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "30%"}
|
||||
return []client.Object{object}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test with replicasLimitWithTraffic - 2",
|
||||
Succeed: false,
|
||||
GetObject: func() []client.Object {
|
||||
object := rollout.DeepCopy()
|
||||
n := len(object.Spec.Strategy.Canary.Steps)
|
||||
object.Spec.Strategy.Canary.Steps[n-1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "31%"}
|
||||
return []client.Object{object}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test with replicasLimitWithTraffic - 2 - canary style",
|
||||
Succeed: true,
|
||||
GetObject: func() []client.Object {
|
||||
object := rollout.DeepCopy()
|
||||
object.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true
|
||||
n := len(object.Spec.Strategy.Canary.Steps)
|
||||
object.Spec.Strategy.Canary.Steps[n-1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "31%"}
|
||||
return []client.Object{object}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test with replicasLimitWithTraffic - 2 - cloneset",
|
||||
Succeed: false,
|
||||
GetObject: func() []client.Object {
|
||||
object := rollout.DeepCopy()
|
||||
object.Spec.WorkloadRef = appsv1beta1.ObjectRef{
|
||||
APIVersion: "apps.kruise.io/v1alpha1",
|
||||
Kind: "CloneSet",
|
||||
Name: "whatever",
|
||||
}
|
||||
n := len(object.Spec.Strategy.Canary.Steps)
|
||||
object.Spec.Strategy.Canary.Steps[n-1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "31%"}
|
||||
return []client.Object{object}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test with replicasLimitWithTraffic - 3",
|
||||
Succeed: true,
|
||||
GetObject: func() []client.Object {
|
||||
object := rollout.DeepCopy()
|
||||
PartitionReplicasLimitWithTraffic = 100
|
||||
n := len(object.Spec.Strategy.Canary.Steps)
|
||||
object.Spec.Strategy.Canary.Steps[n-1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "100%"}
|
||||
return []client.Object{object}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test with replicasLimitWithTraffic - 4",
|
||||
Succeed: false,
|
||||
GetObject: func() []client.Object {
|
||||
object := rollout.DeepCopy()
|
||||
PartitionReplicasLimitWithTraffic = 50
|
||||
n := len(object.Spec.Strategy.Canary.Steps)
|
||||
object.Spec.Strategy.Canary.Steps[n-1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "51%"}
|
||||
return []client.Object{object}
|
||||
},
|
||||
},
|
||||
//{
|
||||
// Name: "The last Steps.Traffic is not 100",
|
||||
// Succeed: false,
|
||||
|
@ -335,6 +461,133 @@ func TestRolloutValidateCreate(t *testing.T) {
|
|||
errList := handler.validateRollout(objects[0].(*appsv1beta1.Rollout))
|
||||
t.Log(errList)
|
||||
Expect(len(errList) == 0).Should(Equal(cs.Succeed))
|
||||
// restore PartitionReplicasLimitWithTraffic after each case
|
||||
PartitionReplicasLimitWithTraffic = 30
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRolloutV1alpha1ValidateCreate(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Succeed bool
|
||||
GetObject func() []client.Object
|
||||
}{
|
||||
{
|
||||
Name: "Canary style",
|
||||
Succeed: true,
|
||||
GetObject: func() []client.Object {
|
||||
obj := rolloutV1alpha1.DeepCopy()
|
||||
return []client.Object{obj}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Partition style without replicas - 1",
|
||||
Succeed: false,
|
||||
GetObject: func() []client.Object {
|
||||
obj := rolloutV1alpha1.DeepCopy()
|
||||
obj.Annotations[appsv1alpha1.RolloutStyleAnnotation] = string(appsv1alpha1.PartitionRollingStyle)
|
||||
return []client.Object{obj}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Partition style without replicas - 2",
|
||||
Succeed: true,
|
||||
GetObject: func() []client.Object {
|
||||
obj := rolloutV1alpha1.DeepCopy()
|
||||
PartitionReplicasLimitWithTraffic = 100
|
||||
obj.Annotations[appsv1alpha1.RolloutStyleAnnotation] = string(appsv1alpha1.PartitionRollingStyle)
|
||||
return []client.Object{obj}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Partition style without replicas - 3",
|
||||
Succeed: false,
|
||||
GetObject: func() []client.Object {
|
||||
obj := rolloutV1alpha1.DeepCopy()
|
||||
obj.Spec.Strategy.Canary.Steps[len(obj.Spec.Strategy.Canary.Steps)-1].Weight = utilpointer.Int32(32)
|
||||
PartitionReplicasLimitWithTraffic = 31
|
||||
obj.Annotations[appsv1alpha1.RolloutStyleAnnotation] = string(appsv1alpha1.PartitionRollingStyle)
|
||||
return []client.Object{obj}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Partition style without replicas- 3 - canary style",
|
||||
Succeed: true,
|
||||
GetObject: func() []client.Object {
|
||||
obj := rolloutV1alpha1.DeepCopy()
|
||||
obj.Spec.Strategy.Canary.Steps[len(obj.Spec.Strategy.Canary.Steps)-1].Weight = utilpointer.Int32(32)
|
||||
PartitionReplicasLimitWithTraffic = 31
|
||||
return []client.Object{obj}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Partition style without replicas - 3 - cloneset",
|
||||
Succeed: false,
|
||||
GetObject: func() []client.Object {
|
||||
obj := rolloutV1alpha1.DeepCopy()
|
||||
obj.Spec.ObjectRef.WorkloadRef = &appsv1alpha1.WorkloadRef{
|
||||
APIVersion: "apps.kruise.io/v1alpha1",
|
||||
Kind: "CloneSet",
|
||||
Name: "whatever",
|
||||
}
|
||||
obj.Spec.Strategy.Canary.Steps[len(obj.Spec.Strategy.Canary.Steps)-1].Weight = utilpointer.Int32(32)
|
||||
PartitionReplicasLimitWithTraffic = 31
|
||||
obj.Annotations[appsv1alpha1.RolloutStyleAnnotation] = string(appsv1alpha1.PartitionRollingStyle)
|
||||
return []client.Object{obj}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Partition style with replicas - 1",
|
||||
Succeed: false,
|
||||
GetObject: func() []client.Object {
|
||||
obj := rolloutV1alpha1.DeepCopy()
|
||||
obj.Spec.Strategy.Canary.Steps[len(obj.Spec.Strategy.Canary.Steps)-1].Weight = utilpointer.Int32(50)
|
||||
obj.Spec.Strategy.Canary.Steps[len(obj.Spec.Strategy.Canary.Steps)-1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "32%"}
|
||||
PartitionReplicasLimitWithTraffic = 31
|
||||
obj.Annotations[appsv1alpha1.RolloutStyleAnnotation] = string(appsv1alpha1.PartitionRollingStyle)
|
||||
return []client.Object{obj}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Partition style with replicas - 2",
|
||||
Succeed: true,
|
||||
GetObject: func() []client.Object {
|
||||
obj := rolloutV1alpha1.DeepCopy()
|
||||
obj.Spec.Strategy.Canary.Steps[len(obj.Spec.Strategy.Canary.Steps)-1].Weight = utilpointer.Int32(50)
|
||||
obj.Spec.Strategy.Canary.Steps[len(obj.Spec.Strategy.Canary.Steps)-1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "31%"}
|
||||
PartitionReplicasLimitWithTraffic = 31
|
||||
obj.Annotations[appsv1alpha1.RolloutStyleAnnotation] = string(appsv1alpha1.PartitionRollingStyle)
|
||||
return []client.Object{obj}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Partition style with replicas - 3",
|
||||
Succeed: true,
|
||||
GetObject: func() []client.Object {
|
||||
obj := rolloutV1alpha1.DeepCopy()
|
||||
obj.Spec.Strategy.Canary.Steps[len(obj.Spec.Strategy.Canary.Steps)-1].Weight = nil
|
||||
obj.Spec.Strategy.Canary.Steps[len(obj.Spec.Strategy.Canary.Steps)-1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "50%"}
|
||||
obj.Annotations[appsv1alpha1.RolloutStyleAnnotation] = string(appsv1alpha1.PartitionRollingStyle)
|
||||
return []client.Object{obj}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, cs := range cases {
|
||||
t.Run(cs.Name, func(t *testing.T) {
|
||||
objects := cs.GetObject()
|
||||
cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
|
||||
handler := RolloutCreateUpdateHandler{
|
||||
Client: cli,
|
||||
}
|
||||
errList := handler.validateV1alpha1Rollout(objects[0].(*appsv1alpha1.Rollout))
|
||||
t.Log(errList)
|
||||
Expect(len(errList) == 0).Should(Equal(cs.Succeed))
|
||||
// restore PartitionReplicasLimitWithTraffic after each case
|
||||
PartitionReplicasLimitWithTraffic = 30
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
appsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
||||
"github.com/openkruise/rollouts/pkg/util"
|
||||
utilclient "github.com/openkruise/rollouts/pkg/util/client"
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
@ -70,7 +71,7 @@ func (h *RolloutCreateUpdateHandler) validateV1alpha1RolloutUpdate(oldObj, newOb
|
|||
}
|
||||
|
||||
func (h *RolloutCreateUpdateHandler) validateV1alpha1Rollout(rollout *appsv1alpha1.Rollout) field.ErrorList {
|
||||
errList := validateV1alpha1RolloutSpec(rollout, field.NewPath("Spec"))
|
||||
errList := validateV1alpha1RolloutSpec(GetContextFromv1alpha1Rollout(rollout), rollout, field.NewPath("Spec"))
|
||||
errList = append(errList, h.validateV1alpha1RolloutConflict(rollout, field.NewPath("Conflict Checker"))...)
|
||||
return errList
|
||||
}
|
||||
|
@ -93,10 +94,10 @@ func (h *RolloutCreateUpdateHandler) validateV1alpha1RolloutConflict(rollout *ap
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateV1alpha1RolloutSpec(rollout *appsv1alpha1.Rollout, fldPath *field.Path) field.ErrorList {
|
||||
func validateV1alpha1RolloutSpec(c *validateContext, rollout *appsv1alpha1.Rollout, fldPath *field.Path) field.ErrorList {
|
||||
errList := validateV1alpha1RolloutSpecObjectRef(&rollout.Spec.ObjectRef, fldPath.Child("ObjectRef"))
|
||||
errList = append(errList, validateV1alpha1RolloutRollingStyle(rollout, field.NewPath("RollingStyle"))...)
|
||||
errList = append(errList, validateV1alpha1RolloutSpecStrategy(&rollout.Spec.Strategy, fldPath.Child("Strategy"))...)
|
||||
errList = append(errList, validateV1alpha1RolloutSpecStrategy(c, &rollout.Spec.Strategy, fldPath.Child("Strategy"))...)
|
||||
return errList
|
||||
}
|
||||
|
||||
|
@ -122,16 +123,16 @@ func validateV1alpha1RolloutSpecObjectRef(objectRef *appsv1alpha1.ObjectRef, fld
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateV1alpha1RolloutSpecStrategy(strategy *appsv1alpha1.RolloutStrategy, fldPath *field.Path) field.ErrorList {
|
||||
return validateV1alpha1RolloutSpecCanaryStrategy(strategy.Canary, fldPath.Child("Canary"))
|
||||
func validateV1alpha1RolloutSpecStrategy(c *validateContext, strategy *appsv1alpha1.RolloutStrategy, fldPath *field.Path) field.ErrorList {
|
||||
return validateV1alpha1RolloutSpecCanaryStrategy(c, strategy.Canary, fldPath.Child("Canary"))
|
||||
}
|
||||
|
||||
func validateV1alpha1RolloutSpecCanaryStrategy(canary *appsv1alpha1.CanaryStrategy, fldPath *field.Path) field.ErrorList {
|
||||
func validateV1alpha1RolloutSpecCanaryStrategy(c *validateContext, canary *appsv1alpha1.CanaryStrategy, fldPath *field.Path) field.ErrorList {
|
||||
if canary == nil {
|
||||
return field.ErrorList{field.Invalid(fldPath, nil, "Canary cannot be empty")}
|
||||
}
|
||||
|
||||
errList := validateV1alpha1RolloutSpecCanarySteps(canary.Steps, fldPath.Child("Steps"), len(canary.TrafficRoutings) > 0)
|
||||
errList := validateV1alpha1RolloutSpecCanarySteps(c, canary.Steps, fldPath.Child("Steps"), len(canary.TrafficRoutings) > 0)
|
||||
if len(canary.TrafficRoutings) > 1 {
|
||||
errList = append(errList, field.Invalid(fldPath, canary.TrafficRoutings, "Rollout currently only support single TrafficRouting."))
|
||||
}
|
||||
|
@ -169,7 +170,7 @@ func validateV1alpha1RolloutSpecCanaryTraffic(traffic appsv1alpha1.TrafficRoutin
|
|||
return errList
|
||||
}
|
||||
|
||||
func validateV1alpha1RolloutSpecCanarySteps(steps []appsv1alpha1.CanaryStep, fldPath *field.Path, isTraffic bool) field.ErrorList {
|
||||
func validateV1alpha1RolloutSpecCanarySteps(c *validateContext, steps []appsv1alpha1.CanaryStep, fldPath *field.Path, isTraffic bool) field.ErrorList {
|
||||
stepCount := len(steps)
|
||||
if stepCount == 0 {
|
||||
return field.ErrorList{field.Invalid(fldPath, steps, "The number of Canary.Steps cannot be empty")}
|
||||
|
@ -188,6 +189,25 @@ func validateV1alpha1RolloutSpecCanarySteps(steps []appsv1alpha1.CanaryStep, fld
|
|||
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("Replicas"),
|
||||
s.Replicas, `replicas must be positive number, or a percentage with "0%" < canaryReplicas <= "100%"`)}
|
||||
}
|
||||
// is partiton-style release
|
||||
// and has traffic strategy for this step
|
||||
// and replicas is in percentage format and greater than replicasLimitWithTraffic
|
||||
if c.style == string(appsv1alpha1.PartitionRollingStyle) &&
|
||||
IsPercentageCanaryReplicasType(s.Replicas) && canaryReplicas > PartitionReplicasLimitWithTraffic &&
|
||||
(s.Matches != nil || s.Weight != nil) {
|
||||
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("steps"), steps,
|
||||
`For patition style rollout: step[x].replicas must not greater than replicasLimitWithTraffic if traffic or matches specified`)}
|
||||
}
|
||||
} else {
|
||||
// replicas is nil, weight is not nil
|
||||
if c.style == string(appsv1alpha1.PartitionRollingStyle) && *s.Weight > int32(PartitionReplicasLimitWithTraffic) {
|
||||
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("steps"), steps,
|
||||
`For patition style rollout: step[x].weight must not greater than replicasLimitWithTraffic if replicas is not specified`)}
|
||||
}
|
||||
if *s.Weight <= 0 || *s.Weight > 100 {
|
||||
return field.ErrorList{field.Invalid(fldPath.Index(i).Child("Weight"), s.Weight,
|
||||
`weight must be positive number, and less than or equal to replicasLimitWithTraffic (defaults to 30)`)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,3 +245,19 @@ func IsSameV1alpha1WorkloadRefGVKName(a, b *appsv1alpha1.WorkloadRef) bool {
|
|||
}
|
||||
return reflect.DeepEqual(a, b)
|
||||
}
|
||||
|
||||
func GetContextFromv1alpha1Rollout(rollout *appsv1alpha1.Rollout) *validateContext {
|
||||
if rollout.Spec.Strategy.Canary == nil {
|
||||
return nil
|
||||
}
|
||||
style := appsv1alpha1.PartitionRollingStyle
|
||||
switch strings.ToLower(rollout.Annotations[appsv1alpha1.RolloutStyleAnnotation]) {
|
||||
case "", strings.ToLower(string(appsv1alpha1.CanaryRollingStyle)):
|
||||
targetRef := rollout.Spec.ObjectRef.WorkloadRef
|
||||
if targetRef.APIVersion == apps.SchemeGroupVersion.String() && targetRef.Kind == reflect.TypeOf(apps.Deployment{}).Name() {
|
||||
style = appsv1alpha1.CanaryRollingStyle
|
||||
}
|
||||
}
|
||||
|
||||
return &validateContext{style: string(style)}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,5 @@
|
|||
# we recommend that new test cases or modifications to existing test cases should
|
||||
# use v1beta1 Rollout, eg. use rollout_v1beta1_canary_base.yaml
|
||||
apiVersion: rollouts.kruise.io/v1alpha1
|
||||
kind: Rollout
|
||||
metadata:
|
||||
|
|
|
@ -14,17 +14,13 @@ spec:
|
|||
- traffic: 20%
|
||||
replicas: 20%
|
||||
pause: {}
|
||||
- traffic: 40%
|
||||
replicas: 40%
|
||||
- replicas: 40%
|
||||
pause: {duration: 10}
|
||||
- traffic: 60%
|
||||
replicas: 60%
|
||||
- replicas: 60%
|
||||
pause: {duration: 10}
|
||||
- traffic: 80%
|
||||
replicas: 80%
|
||||
- replicas: 80%
|
||||
pause: {duration: 10}
|
||||
- traffic: 100%
|
||||
replicas: 100%
|
||||
- replicas: 100%
|
||||
pause: {duration: 0}
|
||||
trafficRoutings:
|
||||
- service: echoserver
|
||||
|
|
Loading…
Reference in New Issue