507 lines
14 KiB
Go
507 lines
14 KiB
Go
package validating
|
|
|
|
import (
|
|
"testing"
|
|
|
|
. "github.com/onsi/ginkgo"
|
|
. "github.com/onsi/gomega"
|
|
appsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
|
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 = appsv1alpha1.Rollout{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: appsv1alpha1.SchemeGroupVersion.String(),
|
|
Kind: "Rollout",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "rollout-demo",
|
|
Namespace: "namespace-unit-test",
|
|
},
|
|
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{
|
|
{
|
|
Weight: utilpointer.Int32Ptr(10),
|
|
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(1)},
|
|
Pause: appsv1alpha1.RolloutPause{},
|
|
},
|
|
{
|
|
Weight: utilpointer.Int32Ptr(10),
|
|
Replicas: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(3)},
|
|
Pause: appsv1alpha1.RolloutPause{Duration: utilpointer.Int32(1 * 24 * 60 * 60)},
|
|
},
|
|
{
|
|
Weight: utilpointer.Int32Ptr(30),
|
|
Pause: appsv1alpha1.RolloutPause{Duration: utilpointer.Int32(7 * 24 * 60 * 60)},
|
|
},
|
|
{
|
|
Weight: utilpointer.Int32Ptr(100),
|
|
},
|
|
},
|
|
TrafficRoutings: []*appsv1alpha1.TrafficRouting{
|
|
{
|
|
Service: "service-demo",
|
|
Ingress: &appsv1alpha1.IngressTrafficRouting{
|
|
ClassType: "nginx",
|
|
Name: "ingress-nginx-demo",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Status: appsv1alpha1.RolloutStatus{
|
|
CanaryStatus: &appsv1alpha1.CanaryStatus{
|
|
CurrentStepState: appsv1alpha1.CanaryStepStateCompleted,
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
_ = appsv1alpha1.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 {
|
|
return []client.Object{rollout.DeepCopy()}
|
|
},
|
|
},
|
|
/***********************************************************
|
|
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",
|
|
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 = []appsv1alpha1.CanaryStep{}
|
|
return []client.Object{object}
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkloadRef is not Deployment kind",
|
|
Succeed: true,
|
|
GetObject: func() []client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Spec.ObjectRef.WorkloadRef = &appsv1alpha1.WorkloadRef{
|
|
APIVersion: "apps/v1",
|
|
Kind: "StatefulSet",
|
|
Name: "whatever",
|
|
}
|
|
return []client.Object{object}
|
|
},
|
|
},
|
|
{
|
|
Name: "Steps.Weight is a decreasing sequence",
|
|
Succeed: false,
|
|
GetObject: func() []client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Spec.Strategy.Canary.Steps[2].Weight = utilpointer.Int32Ptr(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}
|
|
},
|
|
},
|
|
{
|
|
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.Weight is illegal value, 0",
|
|
Succeed: false,
|
|
GetObject: func() []client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Spec.Strategy.Canary.Steps[1].Weight = utilpointer.Int32Ptr(0)
|
|
return []client.Object{object}
|
|
},
|
|
},
|
|
{
|
|
Name: "Steps.Weight is illegal value, 101",
|
|
Succeed: false,
|
|
GetObject: func() []client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Spec.Strategy.Canary.Steps[1].Weight = utilpointer.Int32Ptr(101)
|
|
return []client.Object{object}
|
|
},
|
|
},
|
|
//{
|
|
// Name: "The last Steps.Weight 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].Weight = 80
|
|
// return []client.Object{object}
|
|
// },
|
|
//},
|
|
{
|
|
Name: "Wrong Traffic type",
|
|
Succeed: false,
|
|
GetObject: func() []client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Spec.Strategy.Canary.TrafficRoutings[0].Ingress.ClassType = "Whatever"
|
|
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.ObjectRef.WorkloadRef.Name = "another"
|
|
|
|
object2 := rollout.DeepCopy()
|
|
object2.Name = "object-2"
|
|
object2.Spec.ObjectRef.WorkloadRef.APIVersion = "another"
|
|
|
|
object3 := rollout.DeepCopy()
|
|
object3.Name = "object-3"
|
|
object3.Spec.ObjectRef.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.ObjectRef.WorkloadRef.Name = "another"
|
|
|
|
object2 := rollout.DeepCopy()
|
|
object2.Name = "object-2"
|
|
object2.Spec.ObjectRef.WorkloadRef.APIVersion = "another"
|
|
|
|
object3 := rollout.DeepCopy()
|
|
object3.Name = "object-3"
|
|
object3.Spec.ObjectRef.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].(*appsv1alpha1.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].Weight = utilpointer.Int32Ptr(5)
|
|
return object
|
|
},
|
|
},
|
|
{
|
|
Name: "Rollout is progressing, but spec not changed",
|
|
Succeed: true,
|
|
GetOldObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.Phase = appsv1alpha1.RolloutPhaseProgressing
|
|
return object
|
|
},
|
|
GetNewObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.Phase = appsv1alpha1.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 = appsv1alpha1.RolloutPhaseProgressing
|
|
return object
|
|
},
|
|
GetNewObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.Phase = appsv1alpha1.RolloutPhaseProgressing
|
|
object.Spec.Strategy.Canary.Steps[0].Weight = utilpointer.Int32Ptr(5)
|
|
return object
|
|
},
|
|
},
|
|
{
|
|
Name: "Rollout is terminating, and spec changed",
|
|
Succeed: false,
|
|
GetOldObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.Phase = appsv1alpha1.RolloutPhaseTerminating
|
|
return object
|
|
},
|
|
GetNewObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.Phase = appsv1alpha1.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 = appsv1alpha1.RolloutPhaseInitial
|
|
return object
|
|
},
|
|
GetNewObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.Phase = appsv1alpha1.RolloutPhaseInitial
|
|
object.Spec.Strategy.Canary.Steps[0].Weight = utilpointer.Int32Ptr(5)
|
|
return object
|
|
},
|
|
},
|
|
{
|
|
Name: "Rollout is healthy, and spec changed",
|
|
Succeed: true,
|
|
GetOldObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.Phase = appsv1alpha1.RolloutPhaseHealthy
|
|
return object
|
|
},
|
|
GetNewObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.Phase = appsv1alpha1.RolloutPhaseHealthy
|
|
object.Spec.Strategy.Canary.Steps[0].Weight = utilpointer.Int32Ptr(5)
|
|
return object
|
|
},
|
|
},
|
|
{
|
|
Name: "Rollout canary state: paused -> completed",
|
|
Succeed: true,
|
|
GetOldObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.CanaryStatus.CurrentStepState = appsv1alpha1.CanaryStepStatePaused
|
|
return object
|
|
},
|
|
GetNewObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.CanaryStatus.CurrentStepState = appsv1alpha1.CanaryStepStateCompleted
|
|
return object
|
|
},
|
|
},
|
|
{
|
|
Name: "Rollout canary state: completed -> completed",
|
|
Succeed: true,
|
|
GetOldObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.CanaryStatus.CurrentStepState = appsv1alpha1.CanaryStepStateCompleted
|
|
return object
|
|
},
|
|
GetNewObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.CanaryStatus.CurrentStepState = appsv1alpha1.CanaryStepStateCompleted
|
|
return object
|
|
},
|
|
},
|
|
/*{
|
|
Name: "Rollout canary state: upgrade -> completed",
|
|
Succeed: false,
|
|
GetOldObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.CanaryStatus.CurrentStepState = appsv1alpha1.CanaryStepStateUpgrade
|
|
return object
|
|
},
|
|
GetNewObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.CanaryStatus.CurrentStepState = appsv1alpha1.CanaryStepStateCompleted
|
|
return object
|
|
},
|
|
},
|
|
{
|
|
Name: "Rollout canary state: routing -> completed",
|
|
Succeed: false,
|
|
GetOldObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.CanaryStatus.CurrentStepState = appsv1alpha1.CanaryStepStateTrafficRouting
|
|
return object
|
|
},
|
|
GetNewObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.CanaryStatus.CurrentStepState = appsv1alpha1.CanaryStepStateCompleted
|
|
return object
|
|
},
|
|
},
|
|
{
|
|
Name: "Rollout canary state: analysis -> completed",
|
|
Succeed: false,
|
|
GetOldObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.CanaryStatus.CurrentStepState = appsv1alpha1.CanaryStepStateMetricsAnalysis
|
|
return object
|
|
},
|
|
GetNewObject: func() client.Object {
|
|
object := rollout.DeepCopy()
|
|
object.Status.CanaryStatus.CurrentStepState = appsv1alpha1.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 = appsv1alpha1.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.(*appsv1alpha1.Rollout), newObject.(*appsv1alpha1.Rollout))
|
|
t.Log(errList)
|
|
Expect(len(errList) == 0).Should(Equal(cs.Succeed))
|
|
})
|
|
}
|
|
}
|