rollouts/pkg/webhook/rollout/validating/rollout_create_update_handl...

552 lines
16 KiB
Go

/*
Copyright 2022 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 validating
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
rolloutapi "github.com/openkruise/rollouts/api"
appsv1beta1 "github.com/openkruise/rollouts/api/v1beta1"
apps "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
utilpointer "k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
var (
scheme = runtime.NewScheme()
rollout = appsv1beta1.Rollout{
TypeMeta: metav1.TypeMeta{
APIVersion: appsv1beta1.SchemeGroupVersion.String(),
Kind: "Rollout",
},
ObjectMeta: metav1.ObjectMeta{
Name: "rollout-demo",
Namespace: "namespace-unit-test",
Annotations: map[string]string{},
},
Spec: appsv1beta1.RolloutSpec{
WorkloadRef: appsv1beta1.ObjectRef{
APIVersion: apps.SchemeGroupVersion.String(),
Kind: "Deployment",
Name: "deployment-demo",
},
Strategy: appsv1beta1.RolloutStrategy{
Canary: &appsv1beta1.CanaryStrategy{
Steps: []appsv1beta1.CanaryStep{
{
TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("10%"),
},
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(1)},
Pause: appsv1beta1.RolloutPause{},
},
{
TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("10%"),
},
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(3)},
Pause: appsv1beta1.RolloutPause{Duration: utilpointer.Int32(1 * 24 * 60 * 60)},
},
{
TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("30%"),
},
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(10)},
Pause: appsv1beta1.RolloutPause{Duration: utilpointer.Int32(7 * 24 * 60 * 60)},
},
{
TrafficRoutingStrategy: appsv1beta1.TrafficRoutingStrategy{
Traffic: utilpointer.String("100%"),
},
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(20)},
},
},
TrafficRoutings: []appsv1beta1.TrafficRoutingRef{
{
Service: "service-demo",
Ingress: &appsv1beta1.IngressTrafficRouting{
ClassType: "nginx",
Name: "ingress-nginx-demo",
},
},
},
},
},
},
Status: appsv1beta1.RolloutStatus{
CanaryStatus: &appsv1beta1.CanaryStatus{
CurrentStepState: appsv1beta1.CanaryStepStateCompleted,
},
},
}
)
func init() {
_ = rolloutapi.AddToScheme(scheme)
}
func TestRolloutValidateCreate(t *testing.T) {
RegisterFailHandler(Fail)
cases := []struct {
Name string
Succeed bool
GetObject func() []client.Object
}{
{
Name: "Normal case",
Succeed: true,
GetObject: func() []client.Object {
obj := rollout.DeepCopy()
return []client.Object{obj}
},
},
/***********************************************************
The following cases may lead to controller panic
**********************************************************/
{
Name: "Canary is nil",
Succeed: false,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary = nil
return []client.Object{object}
},
},
/****************************************************************
The following cases may lead to that controller cannot work
***************************************************************/
{
Name: "Service name is empty",
Succeed: false,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary.TrafficRoutings[0].Service = ""
return []client.Object{object}
},
},
{
Name: "Nginx ingress name is empty",
Succeed: false,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary.TrafficRoutings[0].Ingress.Name = ""
return []client.Object{object}
},
},
{
Name: "Steps is empty",
Succeed: false,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary.Steps = []appsv1beta1.CanaryStep{}
return []client.Object{object}
},
},
{
Name: "WorkloadRef is not Deployment kind",
Succeed: true,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.WorkloadRef = appsv1beta1.ObjectRef{
APIVersion: "apps/v1",
Kind: "StatefulSet",
Name: "whatever",
}
return []client.Object{object}
},
},
{
Name: "Steps.Traffic is a decreasing sequence",
Succeed: false,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary.Steps[2].Traffic = utilpointer.String("%5")
return []client.Object{object}
},
},
{
Name: "Steps.Replicas is illegal value, '50'",
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}
},
},
{
Name: "Steps.Replicas is illegal value, '101%'",
Succeed: false,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary.Steps[1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "101%"}
return []client.Object{object}
},
},
{
Name: "Steps.Replicas is illegal value, '0%'",
Succeed: false,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary.Steps[1].Replicas = &intstr.IntOrString{Type: intstr.String, StrVal: "0%"}
return []client.Object{object}
},
},
{
Name: "Steps.Traffic is illegal value, 0",
Succeed: false,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary.Steps[1].Traffic = utilpointer.String("0%")
return []client.Object{object}
},
},
{
Name: "Steps.Traffic is illegal value, 101",
Succeed: false,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary.Steps[1].Traffic = utilpointer.String("101%")
return []client.Object{object}
},
},
{
Name: "Canary rolling style",
Succeed: true,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true
return []client.Object{object}
},
},
{
Name: "Partition rolling style",
Succeed: true,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
return []client.Object{object}
},
},
{
Name: "Miss matched rolling style",
Succeed: false,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true
object.Spec.WorkloadRef.APIVersion = "apps.kruise.io/v1alpha1"
object.Spec.WorkloadRef.Kind = "CloneSet"
return []client.Object{object}
},
},
//{
// Name: "The last Steps.Traffic is not 100",
// Succeed: false,
// GetObject: func() []client.Object {
// object := rollout.DeepCopy()
// n := len(object.Spec.Strategy.Canary.Steps)
// object.Spec.Strategy.Canary.Steps[n-1].Traffic = 80
// return []client.Object{object}
// },
//},
/****************************************************************
The following cases are conflict cases
***************************************************************/
{
Name: "Without conflict",
Succeed: true,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object1 := rollout.DeepCopy()
object1.Name = "object-1"
object1.Spec.WorkloadRef.Name = "another"
object2 := rollout.DeepCopy()
object2.Name = "object-2"
object2.Spec.WorkloadRef.APIVersion = "another"
object3 := rollout.DeepCopy()
object3.Name = "object-3"
object3.Spec.WorkloadRef.Kind = "another"
return []client.Object{
object, object1, object2, object3,
}
},
},
{
Name: "With conflict",
Succeed: false,
GetObject: func() []client.Object {
object := rollout.DeepCopy()
object1 := rollout.DeepCopy()
object1.Name = "object-1"
object1.Spec.WorkloadRef.Name = "another"
object2 := rollout.DeepCopy()
object2.Name = "object-2"
object2.Spec.WorkloadRef.APIVersion = "another"
object3 := rollout.DeepCopy()
object3.Name = "object-3"
object3.Spec.WorkloadRef.Kind = "another"
object4 := rollout.DeepCopy()
object4.Name = "object-4"
return []client.Object{
object, object1, object2, object3, object4,
}
},
},
}
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.validateRollout(objects[0].(*appsv1beta1.Rollout))
t.Log(errList)
Expect(len(errList) == 0).Should(Equal(cs.Succeed))
})
}
}
func TestRolloutValidateUpdate(t *testing.T) {
RegisterFailHandler(Fail)
cases := []struct {
Name string
Succeed bool
GetOldObject func() client.Object
GetNewObject func() client.Object
}{
{
Name: "Normal case",
Succeed: true,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Spec.Strategy.Canary.Steps[0].Traffic = utilpointer.String("5%")
return object
},
},
{
Name: "Rollout is progressing, but spec not changed",
Succeed: true,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.Phase = appsv1beta1.RolloutPhaseProgressing
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.Phase = appsv1beta1.RolloutPhaseProgressing
object.Status.CanaryStatus.CurrentStepIndex = 1
return object
},
},
{
Name: "Rollout is progressing, and spec changed",
Succeed: true,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.Phase = appsv1beta1.RolloutPhaseProgressing
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.Phase = appsv1beta1.RolloutPhaseProgressing
object.Spec.Strategy.Canary.Steps[0].Traffic = utilpointer.String("5%")
return object
},
},
{
Name: "Rollout is progressing, and rolling style changed",
Succeed: false,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.Phase = appsv1beta1.RolloutPhaseProgressing
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.Phase = appsv1beta1.RolloutPhaseProgressing
object.Spec.Strategy.Canary.EnableExtraWorkloadForCanary = true
return object
},
},
{
Name: "Rollout is terminating, and spec changed",
Succeed: false,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.Phase = appsv1beta1.RolloutPhaseTerminating
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.Phase = appsv1beta1.RolloutPhaseTerminating
object.Spec.Strategy.Canary.TrafficRoutings[0].Ingress.ClassType = "alb"
return object
},
},
{
Name: "Rollout is initial, and spec changed",
Succeed: true,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.Phase = appsv1beta1.RolloutPhaseInitial
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.Phase = appsv1beta1.RolloutPhaseInitial
object.Spec.Strategy.Canary.Steps[0].Traffic = utilpointer.String("5%")
return object
},
},
{
Name: "Rollout is healthy, and spec changed",
Succeed: true,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.Phase = appsv1beta1.RolloutPhaseHealthy
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.Phase = appsv1beta1.RolloutPhaseHealthy
object.Spec.Strategy.Canary.Steps[0].Traffic = utilpointer.String("5%")
return object
},
},
{
Name: "Rollout canary state: paused -> completed",
Succeed: true,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.CanaryStatus.CurrentStepState = appsv1beta1.CanaryStepStatePaused
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.CanaryStatus.CurrentStepState = appsv1beta1.CanaryStepStateCompleted
return object
},
},
{
Name: "Rollout canary state: completed -> completed",
Succeed: true,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.CanaryStatus.CurrentStepState = appsv1beta1.CanaryStepStateCompleted
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.CanaryStatus.CurrentStepState = appsv1beta1.CanaryStepStateCompleted
return object
},
},
/*{
Name: "Rollout canary state: upgrade -> completed",
Succeed: false,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.CanaryStatus.CurrentStepState = appsv1beta1.CanaryStepStateUpgrade
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.CanaryStatus.CurrentStepState = appsv1beta1.CanaryStepStateCompleted
return object
},
},
{
Name: "Rollout canary state: routing -> completed",
Succeed: false,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.CanaryStatus.CurrentStepState = appsv1beta1.CanaryStepStateTrafficRouting
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.CanaryStatus.CurrentStepState = appsv1beta1.CanaryStepStateCompleted
return object
},
},
{
Name: "Rollout canary state: analysis -> completed",
Succeed: false,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.CanaryStatus.CurrentStepState = appsv1beta1.CanaryStepStateMetricsAnalysis
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.CanaryStatus.CurrentStepState = appsv1beta1.CanaryStepStateCompleted
return object
},
},
{
Name: "Rollout canary state: others -> completed",
Succeed: false,
GetOldObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.CanaryStatus.CurrentStepState = "Whatever"
return object
},
GetNewObject: func() client.Object {
object := rollout.DeepCopy()
object.Status.CanaryStatus.CurrentStepState = appsv1beta1.CanaryStepStateCompleted
return object
},
},*/
}
for _, cs := range cases {
t.Run(cs.Name, func(t *testing.T) {
oldObject := cs.GetOldObject()
newObject := cs.GetNewObject()
cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(oldObject).Build()
handler := RolloutCreateUpdateHandler{
Client: cli,
}
errList := handler.validateRolloutUpdate(oldObject.(*appsv1beta1.Rollout), newObject.(*appsv1beta1.Rollout))
t.Log(errList)
Expect(len(errList) == 0).Should(Equal(cs.Succeed))
})
}
}