552 lines
16 KiB
Go
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))
|
|
})
|
|
}
|
|
}
|