1068 lines
30 KiB
Go
1068 lines
30 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 rollouthistory
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/openkruise/kruise-api/apps/pub"
|
|
kruisev1alpha1 "github.com/openkruise/kruise-api/apps/v1alpha1"
|
|
rolloutapi "github.com/openkruise/rollouts/api"
|
|
rolloutv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
networkingv1 "k8s.io/api/networking/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
|
utilpointer "k8s.io/utils/pointer"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
"sigs.k8s.io/gateway-api/apis/v1alpha2"
|
|
)
|
|
|
|
func init() {
|
|
scheme = runtime.NewScheme()
|
|
_ = clientgoscheme.AddToScheme(scheme)
|
|
_ = kruisev1alpha1.AddToScheme(scheme)
|
|
_ = rolloutapi.AddToScheme(scheme)
|
|
_ = v1alpha2.AddToScheme(scheme)
|
|
}
|
|
|
|
var (
|
|
scheme *runtime.Scheme
|
|
|
|
rollouthistoryDemo = rolloutv1alpha1.RolloutHistory{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "RolloutHistory",
|
|
APIVersion: "rollouts.kruise.io/v1alpha1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "rollouthistory-demo",
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
rolloutIDLabel: "1",
|
|
rolloutNameLabel: "rollout-demo",
|
|
},
|
|
},
|
|
Spec: rolloutv1alpha1.RolloutHistorySpec{
|
|
Rollout: rolloutv1alpha1.RolloutInfo{
|
|
RolloutID: "1",
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "rollout-demo",
|
|
},
|
|
},
|
|
Service: rolloutv1alpha1.ServiceInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "service-demo",
|
|
},
|
|
},
|
|
TrafficRouting: rolloutv1alpha1.TrafficRoutingInfo{
|
|
Ingress: &rolloutv1alpha1.IngressInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "ingress-demo",
|
|
},
|
|
},
|
|
HTTPRoute: &rolloutv1alpha1.HTTPRouteInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "HTTPRoute-demo",
|
|
},
|
|
},
|
|
},
|
|
Workload: rolloutv1alpha1.WorkloadInfo{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: "apps.kruise.io/v1alpha1",
|
|
Kind: "CloneSet",
|
|
},
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "workload-demo",
|
|
},
|
|
},
|
|
},
|
|
Status: rolloutv1alpha1.RolloutHistoryStatus{
|
|
Phase: "",
|
|
CanarySteps: []rolloutv1alpha1.CanaryStepInfo{
|
|
{
|
|
CanaryStepIndex: 1,
|
|
Pods: []rolloutv1alpha1.Pod{
|
|
{
|
|
Name: "pod-1",
|
|
IP: "1.2.3.4",
|
|
NodeName: "local",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
rolloutDemo1 = rolloutv1alpha1.Rollout{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "Rollout",
|
|
APIVersion: "rollouts.kruise.io/v1alpha1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "rollout-demo",
|
|
Namespace: "default",
|
|
Labels: map[string]string{},
|
|
},
|
|
Spec: rolloutv1alpha1.RolloutSpec{
|
|
ObjectRef: rolloutv1alpha1.ObjectRef{
|
|
WorkloadRef: &rolloutv1alpha1.WorkloadRef{
|
|
APIVersion: "apps.kruise.io/v1alpha1",
|
|
Kind: "CloneSet",
|
|
Name: "workload-demo",
|
|
},
|
|
},
|
|
DeprecatedRolloutID: "1",
|
|
Strategy: rolloutv1alpha1.RolloutStrategy{
|
|
Canary: &rolloutv1alpha1.CanaryStrategy{
|
|
Steps: []rolloutv1alpha1.CanaryStep{
|
|
{
|
|
TrafficRoutingStrategy: rolloutv1alpha1.TrafficRoutingStrategy{
|
|
Weight: utilpointer.Int32(5),
|
|
},
|
|
Pause: rolloutv1alpha1.RolloutPause{
|
|
Duration: utilpointer.Int32(0),
|
|
},
|
|
},
|
|
{
|
|
TrafficRoutingStrategy: rolloutv1alpha1.TrafficRoutingStrategy{
|
|
Weight: utilpointer.Int32(40),
|
|
},
|
|
Pause: rolloutv1alpha1.RolloutPause{},
|
|
},
|
|
{
|
|
TrafficRoutingStrategy: rolloutv1alpha1.TrafficRoutingStrategy{
|
|
Weight: utilpointer.Int32(100),
|
|
},
|
|
Pause: rolloutv1alpha1.RolloutPause{
|
|
Duration: utilpointer.Int32(0),
|
|
},
|
|
},
|
|
},
|
|
TrafficRoutings: []rolloutv1alpha1.TrafficRoutingRef{
|
|
{
|
|
Service: "service-demo",
|
|
Ingress: &rolloutv1alpha1.IngressTrafficRouting{
|
|
ClassType: "nginx",
|
|
Name: "ingress-demo",
|
|
},
|
|
Gateway: &rolloutv1alpha1.GatewayTrafficRouting{
|
|
HTTPRouteName: utilpointer.String("HTTPRoute-demo"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
cloneSetDemo = kruisev1alpha1.CloneSet{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: "apps.kruise.io/v1alpha1",
|
|
Kind: "CloneSet",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "workload-demo",
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app": "echoserver",
|
|
},
|
|
},
|
|
Spec: kruisev1alpha1.CloneSetSpec{
|
|
UpdateStrategy: kruisev1alpha1.CloneSetUpdateStrategy{
|
|
Type: "InPlaceIfPossible",
|
|
InPlaceUpdateStrategy: &pub.InPlaceUpdateStrategy{
|
|
GracePeriodSeconds: 1,
|
|
},
|
|
},
|
|
Replicas: utilpointer.Int32(5),
|
|
Selector: &metav1.LabelSelector{
|
|
MatchLabels: map[string]string{
|
|
"app": "echoserver",
|
|
},
|
|
},
|
|
Template: corev1.PodTemplateSpec{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"app": "echoserver",
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "echoserver",
|
|
Image: "cilium/echoserver:1.10.1",
|
|
ImagePullPolicy: "IfNotPresent",
|
|
Ports: []corev1.ContainerPort{
|
|
{
|
|
ContainerPort: 8080,
|
|
},
|
|
},
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "PORT",
|
|
Value: "8080",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
serviceDemo = corev1.Service{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "Service",
|
|
APIVersion: "v1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "service-demo",
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app": "echoserver",
|
|
},
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: []corev1.ServicePort{
|
|
{
|
|
Port: int32(80),
|
|
TargetPort: intstr.IntOrString{IntVal: int32(8080)},
|
|
Name: "http",
|
|
Protocol: "TCP",
|
|
},
|
|
},
|
|
Selector: map[string]string{
|
|
"app": "echoserver",
|
|
},
|
|
},
|
|
}
|
|
|
|
ingressDemo = networkingv1.Ingress{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: "networking.k8s.io/v1",
|
|
Kind: "Ingress",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "ingress-demo",
|
|
Namespace: "default",
|
|
Annotations: map[string]string{
|
|
"kubernetes.io/ingress.class": "nginx",
|
|
},
|
|
},
|
|
Spec: networkingv1.IngressSpec{
|
|
Rules: []networkingv1.IngressRule{
|
|
{
|
|
Host: "echoserver.example.com",
|
|
IngressRuleValue: networkingv1.IngressRuleValue{
|
|
HTTP: &networkingv1.HTTPIngressRuleValue{
|
|
Paths: []networkingv1.HTTPIngressPath{
|
|
{
|
|
Path: "/apis/echo",
|
|
PathType: (*networkingv1.PathType)(utilpointer.String("Exact")),
|
|
Backend: networkingv1.IngressBackend{
|
|
Service: &networkingv1.IngressServiceBackend{
|
|
Name: "service-demo",
|
|
Port: networkingv1.ServiceBackendPort{
|
|
Number: int32(80),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
httpRouteDemo = v1alpha2.HTTPRoute{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: "gateway.networking.k8s.io/v1alpha2",
|
|
Kind: "HTTPRoute",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "HTTPRoute-demo",
|
|
Namespace: "default",
|
|
},
|
|
Spec: v1alpha2.HTTPRouteSpec{
|
|
CommonRouteSpec: v1alpha2.CommonRouteSpec{
|
|
ParentRefs: []v1alpha2.ParentReference{
|
|
{
|
|
Name: "demo-lb",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
podDemo = corev1.Pod{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: "v1",
|
|
Kind: "Pod",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod-demo",
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
rolloutv1alpha1.RolloutBatchIDLabel: "1",
|
|
rolloutv1alpha1.RolloutIDLabel: "1",
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
NodeName: "local",
|
|
},
|
|
}
|
|
)
|
|
|
|
func TestReconcile(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
req ctrl.Request
|
|
getPods func() []*corev1.Pod
|
|
getService func() []*corev1.Service
|
|
getWorkload func() []*kruisev1alpha1.CloneSet
|
|
getIngress func() []*networkingv1.Ingress
|
|
getHTTPRoute func() []*v1alpha2.HTTPRoute
|
|
getRollout func() []*rolloutv1alpha1.Rollout
|
|
getRolloutHistory func() []*rolloutv1alpha1.RolloutHistory
|
|
expectRolloutHistory func() []*rolloutv1alpha1.RolloutHistory
|
|
}{
|
|
{
|
|
name: "test1, create a new rolloutHistory for rollout",
|
|
req: ctrl.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: "default",
|
|
Name: "rollout-demo",
|
|
},
|
|
},
|
|
getPods: func() []*corev1.Pod {
|
|
pods := []*corev1.Pod{}
|
|
return pods
|
|
},
|
|
getService: func() []*corev1.Service {
|
|
services := []*corev1.Service{}
|
|
return services
|
|
},
|
|
getWorkload: func() []*kruisev1alpha1.CloneSet {
|
|
workloads := []*kruisev1alpha1.CloneSet{}
|
|
return workloads
|
|
},
|
|
getIngress: func() []*networkingv1.Ingress {
|
|
ingresses := []*networkingv1.Ingress{}
|
|
return ingresses
|
|
},
|
|
getHTTPRoute: func() []*v1alpha2.HTTPRoute {
|
|
httpRoutes := []*v1alpha2.HTTPRoute{}
|
|
return httpRoutes
|
|
},
|
|
getRollout: func() []*rolloutv1alpha1.Rollout {
|
|
rollout := rolloutDemo1.DeepCopy()
|
|
rollout.Status = rolloutv1alpha1.RolloutStatus{
|
|
CanaryStatus: &rolloutv1alpha1.CanaryStatus{
|
|
ObservedRolloutID: "1",
|
|
},
|
|
Phase: rolloutv1alpha1.RolloutPhaseProgressing,
|
|
}
|
|
return []*rolloutv1alpha1.Rollout{rollout}
|
|
},
|
|
getRolloutHistory: func() []*rolloutv1alpha1.RolloutHistory {
|
|
rollouthistories := []*rolloutv1alpha1.RolloutHistory{}
|
|
return rollouthistories
|
|
},
|
|
expectRolloutHistory: func() []*rolloutv1alpha1.RolloutHistory {
|
|
rollouthistory := rollouthistoryDemo.DeepCopy()
|
|
rollouthistory.Spec = rolloutv1alpha1.RolloutHistorySpec{}
|
|
rollouthistory.Status = rolloutv1alpha1.RolloutHistoryStatus{}
|
|
return []*rolloutv1alpha1.RolloutHistory{rollouthistory}
|
|
},
|
|
},
|
|
{
|
|
name: "test2, completed a rolloutHistory for rollout",
|
|
req: ctrl.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: "default",
|
|
Name: "rollout-demo",
|
|
},
|
|
},
|
|
getPods: func() []*corev1.Pod {
|
|
pod1 := podDemo.DeepCopy()
|
|
pod1.Name = "pod1"
|
|
pod1.Spec.NodeName = "local"
|
|
pod1.Status = corev1.PodStatus{
|
|
PodIP: "1.2.3.1",
|
|
}
|
|
pod1.Labels = map[string]string{
|
|
rolloutv1alpha1.RolloutBatchIDLabel: "1",
|
|
rolloutv1alpha1.RolloutIDLabel: "2",
|
|
"app": "echoserver",
|
|
}
|
|
|
|
pod2 := podDemo.DeepCopy()
|
|
pod2.Name = "pod2"
|
|
pod2.Spec.NodeName = "local"
|
|
pod2.Status = corev1.PodStatus{
|
|
PodIP: "1.2.3.2",
|
|
}
|
|
pod2.Labels = map[string]string{
|
|
rolloutv1alpha1.RolloutBatchIDLabel: "2",
|
|
rolloutv1alpha1.RolloutIDLabel: "2",
|
|
"app": "echoserver",
|
|
}
|
|
|
|
pod3 := podDemo.DeepCopy()
|
|
pod3.Name = "pod3"
|
|
pod3.Spec.NodeName = "local"
|
|
pod3.Status = corev1.PodStatus{
|
|
PodIP: "1.2.3.3",
|
|
}
|
|
pod3.Labels = map[string]string{
|
|
rolloutv1alpha1.RolloutBatchIDLabel: "3",
|
|
rolloutv1alpha1.RolloutIDLabel: "2",
|
|
"app": "echoserver",
|
|
}
|
|
|
|
pod4 := podDemo.DeepCopy()
|
|
pod4.Name = "pod4"
|
|
pod4.Spec.NodeName = "local"
|
|
pod4.Status = corev1.PodStatus{
|
|
PodIP: "1.2.3.4",
|
|
}
|
|
pod4.Labels = map[string]string{
|
|
rolloutv1alpha1.RolloutBatchIDLabel: "3",
|
|
rolloutv1alpha1.RolloutIDLabel: "2",
|
|
"app": "echoserver",
|
|
}
|
|
|
|
pod5 := podDemo.DeepCopy()
|
|
pod5.Name = "pod5"
|
|
pod5.Spec.NodeName = "local"
|
|
pod5.Status = corev1.PodStatus{
|
|
PodIP: "1.2.3.5",
|
|
}
|
|
pod5.Labels = map[string]string{
|
|
rolloutv1alpha1.RolloutBatchIDLabel: "3",
|
|
rolloutv1alpha1.RolloutIDLabel: "2",
|
|
"app": "echoserver",
|
|
}
|
|
|
|
return []*corev1.Pod{pod1, pod2, pod3, pod4, pod5}
|
|
},
|
|
getService: func() []*corev1.Service {
|
|
services := []*corev1.Service{serviceDemo.DeepCopy()}
|
|
return services
|
|
},
|
|
getWorkload: func() []*kruisev1alpha1.CloneSet {
|
|
workloads := []*kruisev1alpha1.CloneSet{cloneSetDemo.DeepCopy()}
|
|
return workloads
|
|
},
|
|
getIngress: func() []*networkingv1.Ingress {
|
|
ingresses := []*networkingv1.Ingress{ingressDemo.DeepCopy()}
|
|
return ingresses
|
|
},
|
|
getHTTPRoute: func() []*v1alpha2.HTTPRoute {
|
|
httpRoutes := []*v1alpha2.HTTPRoute{httpRouteDemo.DeepCopy()}
|
|
return httpRoutes
|
|
},
|
|
getRollout: func() []*rolloutv1alpha1.Rollout {
|
|
rollout := rolloutDemo1.DeepCopy()
|
|
rollout.Spec.DeprecatedRolloutID = "2"
|
|
rollout.Status = rolloutv1alpha1.RolloutStatus{
|
|
CanaryStatus: &rolloutv1alpha1.CanaryStatus{
|
|
ObservedRolloutID: "2",
|
|
},
|
|
Phase: rolloutv1alpha1.RolloutPhaseHealthy,
|
|
}
|
|
return []*rolloutv1alpha1.Rollout{rollout}
|
|
},
|
|
getRolloutHistory: func() []*rolloutv1alpha1.RolloutHistory {
|
|
rollouthistory := rollouthistoryDemo.DeepCopy()
|
|
rollouthistory.Labels = map[string]string{
|
|
rolloutIDLabel: "2",
|
|
rolloutNameLabel: "rollout-demo",
|
|
}
|
|
rollouthistory.Spec = rolloutv1alpha1.RolloutHistorySpec{}
|
|
rollouthistory.Status = rolloutv1alpha1.RolloutHistoryStatus{}
|
|
return []*rolloutv1alpha1.RolloutHistory{rollouthistory}
|
|
},
|
|
expectRolloutHistory: func() []*rolloutv1alpha1.RolloutHistory {
|
|
rollouthistory := rollouthistoryDemo.DeepCopy()
|
|
rollouthistory.Labels = map[string]string{
|
|
rolloutIDLabel: "2",
|
|
rolloutNameLabel: "rollout-demo",
|
|
}
|
|
rollouthistory.Spec = rolloutv1alpha1.RolloutHistorySpec{
|
|
Rollout: rolloutv1alpha1.RolloutInfo{
|
|
RolloutID: "2",
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "rollout-demo",
|
|
},
|
|
},
|
|
Workload: rolloutv1alpha1.WorkloadInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "workload-demo",
|
|
},
|
|
},
|
|
Service: rolloutv1alpha1.ServiceInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "service-demo",
|
|
},
|
|
},
|
|
TrafficRouting: rolloutv1alpha1.TrafficRoutingInfo{
|
|
Ingress: &rolloutv1alpha1.IngressInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "ingress-demo",
|
|
},
|
|
},
|
|
HTTPRoute: &rolloutv1alpha1.HTTPRouteInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "HTTPRoute-demo",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rollouthistory.Status = rolloutv1alpha1.RolloutHistoryStatus{
|
|
Phase: rolloutv1alpha1.PhaseCompleted,
|
|
CanarySteps: []rolloutv1alpha1.CanaryStepInfo{
|
|
{
|
|
CanaryStepIndex: 1,
|
|
Pods: []rolloutv1alpha1.Pod{
|
|
{
|
|
Name: "pod1",
|
|
IP: "1.2.3.1",
|
|
NodeName: "local",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
CanaryStepIndex: 2,
|
|
Pods: []rolloutv1alpha1.Pod{
|
|
{
|
|
Name: "pod2",
|
|
IP: "1.2.3.2",
|
|
NodeName: "local",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
CanaryStepIndex: 3,
|
|
Pods: []rolloutv1alpha1.Pod{
|
|
{
|
|
Name: "pod3",
|
|
IP: "1.2.3.3",
|
|
NodeName: "local",
|
|
},
|
|
{
|
|
Name: "pod4",
|
|
IP: "1.2.3.4",
|
|
NodeName: "local",
|
|
},
|
|
{
|
|
Name: "pod5",
|
|
IP: "1.2.3.5",
|
|
NodeName: "local",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return []*rolloutv1alpha1.RolloutHistory{rollouthistory}
|
|
},
|
|
},
|
|
{
|
|
name: "test3, don't create a new rolloutHistory for rollout without rolloutID or ObservedRolloutID",
|
|
req: ctrl.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: "default",
|
|
Name: "rollout-demo",
|
|
},
|
|
},
|
|
getPods: func() []*corev1.Pod {
|
|
pods := []*corev1.Pod{}
|
|
return pods
|
|
},
|
|
getService: func() []*corev1.Service {
|
|
services := []*corev1.Service{}
|
|
return services
|
|
},
|
|
getWorkload: func() []*kruisev1alpha1.CloneSet {
|
|
workloads := []*kruisev1alpha1.CloneSet{}
|
|
return workloads
|
|
},
|
|
getIngress: func() []*networkingv1.Ingress {
|
|
ingresses := []*networkingv1.Ingress{}
|
|
return ingresses
|
|
},
|
|
getHTTPRoute: func() []*v1alpha2.HTTPRoute {
|
|
httpRoutes := []*v1alpha2.HTTPRoute{}
|
|
return httpRoutes
|
|
},
|
|
getRollout: func() []*rolloutv1alpha1.Rollout {
|
|
rollout := rolloutDemo1.DeepCopy()
|
|
rollout.Spec.DeprecatedRolloutID = ""
|
|
rollout.Status = rolloutv1alpha1.RolloutStatus{
|
|
CanaryStatus: &rolloutv1alpha1.CanaryStatus{
|
|
ObservedRolloutID: "",
|
|
},
|
|
Phase: rolloutv1alpha1.RolloutPhaseProgressing,
|
|
}
|
|
return []*rolloutv1alpha1.Rollout{rollout}
|
|
},
|
|
getRolloutHistory: func() []*rolloutv1alpha1.RolloutHistory {
|
|
rollouthistories := []*rolloutv1alpha1.RolloutHistory{}
|
|
return rollouthistories
|
|
},
|
|
expectRolloutHistory: func() []*rolloutv1alpha1.RolloutHistory {
|
|
rollouthistories := []*rolloutv1alpha1.RolloutHistory{}
|
|
return rollouthistories
|
|
},
|
|
},
|
|
{
|
|
name: "test4, don't create a new rolloutHistory for rollout which doesn't change its rolloutID",
|
|
req: ctrl.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: "default",
|
|
Name: "rollout-demo",
|
|
},
|
|
},
|
|
getPods: func() []*corev1.Pod {
|
|
pods := []*corev1.Pod{}
|
|
return pods
|
|
},
|
|
getService: func() []*corev1.Service {
|
|
services := []*corev1.Service{}
|
|
return services
|
|
},
|
|
getWorkload: func() []*kruisev1alpha1.CloneSet {
|
|
workloads := []*kruisev1alpha1.CloneSet{}
|
|
return workloads
|
|
},
|
|
getIngress: func() []*networkingv1.Ingress {
|
|
ingresses := []*networkingv1.Ingress{}
|
|
return ingresses
|
|
},
|
|
getHTTPRoute: func() []*v1alpha2.HTTPRoute {
|
|
httpRoutes := []*v1alpha2.HTTPRoute{}
|
|
return httpRoutes
|
|
},
|
|
getRollout: func() []*rolloutv1alpha1.Rollout {
|
|
rollout := rolloutDemo1.DeepCopy()
|
|
rollout.Spec.DeprecatedRolloutID = "4"
|
|
rollout.Status = rolloutv1alpha1.RolloutStatus{
|
|
CanaryStatus: &rolloutv1alpha1.CanaryStatus{
|
|
ObservedRolloutID: "4",
|
|
},
|
|
Phase: rolloutv1alpha1.RolloutPhaseProgressing,
|
|
}
|
|
return []*rolloutv1alpha1.Rollout{rollout}
|
|
},
|
|
getRolloutHistory: func() []*rolloutv1alpha1.RolloutHistory {
|
|
rollouthistory := rollouthistoryDemo.DeepCopy()
|
|
rollouthistory.Labels = map[string]string{
|
|
rolloutIDLabel: "4",
|
|
rolloutNameLabel: "rollout-demo",
|
|
}
|
|
rollouthistory.Spec = rolloutv1alpha1.RolloutHistorySpec{
|
|
Rollout: rolloutv1alpha1.RolloutInfo{
|
|
RolloutID: "4",
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "rollout-demo",
|
|
},
|
|
},
|
|
Workload: rolloutv1alpha1.WorkloadInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "workload-demo",
|
|
},
|
|
},
|
|
Service: rolloutv1alpha1.ServiceInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "service-demo",
|
|
},
|
|
},
|
|
TrafficRouting: rolloutv1alpha1.TrafficRoutingInfo{
|
|
Ingress: &rolloutv1alpha1.IngressInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "ingress-demo",
|
|
},
|
|
},
|
|
HTTPRoute: &rolloutv1alpha1.HTTPRouteInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "HTTPRoute-demo",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rollouthistory.Status = rolloutv1alpha1.RolloutHistoryStatus{
|
|
Phase: rolloutv1alpha1.PhaseCompleted,
|
|
CanarySteps: []rolloutv1alpha1.CanaryStepInfo{
|
|
{
|
|
CanaryStepIndex: 1,
|
|
Pods: []rolloutv1alpha1.Pod{
|
|
{
|
|
Name: "pod1",
|
|
IP: "1.2.3.1",
|
|
NodeName: "local",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
CanaryStepIndex: 2,
|
|
Pods: []rolloutv1alpha1.Pod{
|
|
{
|
|
Name: "pod2",
|
|
IP: "1.2.3.2",
|
|
NodeName: "local",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
CanaryStepIndex: 3,
|
|
Pods: []rolloutv1alpha1.Pod{
|
|
{
|
|
Name: "pod3",
|
|
IP: "1.2.3.3",
|
|
NodeName: "local",
|
|
},
|
|
{
|
|
Name: "pod4",
|
|
IP: "1.2.3.4",
|
|
NodeName: "local",
|
|
},
|
|
{
|
|
Name: "pod5",
|
|
IP: "1.2.3.5",
|
|
NodeName: "local",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return []*rolloutv1alpha1.RolloutHistory{rollouthistory}
|
|
},
|
|
expectRolloutHistory: func() []*rolloutv1alpha1.RolloutHistory {
|
|
rollouthistory := rollouthistoryDemo.DeepCopy()
|
|
rollouthistory.Labels = map[string]string{
|
|
rolloutIDLabel: "4",
|
|
rolloutNameLabel: "rollout-demo",
|
|
}
|
|
rollouthistory.Spec = rolloutv1alpha1.RolloutHistorySpec{
|
|
Rollout: rolloutv1alpha1.RolloutInfo{
|
|
RolloutID: "4",
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "rollout-demo",
|
|
},
|
|
},
|
|
Workload: rolloutv1alpha1.WorkloadInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "workload-demo",
|
|
},
|
|
},
|
|
Service: rolloutv1alpha1.ServiceInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "service-demo",
|
|
},
|
|
},
|
|
TrafficRouting: rolloutv1alpha1.TrafficRoutingInfo{
|
|
Ingress: &rolloutv1alpha1.IngressInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "ingress-demo",
|
|
},
|
|
},
|
|
HTTPRoute: &rolloutv1alpha1.HTTPRouteInfo{
|
|
NameAndSpecData: rolloutv1alpha1.NameAndSpecData{
|
|
Name: "HTTPRoute-demo",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rollouthistory.Status = rolloutv1alpha1.RolloutHistoryStatus{
|
|
Phase: rolloutv1alpha1.PhaseCompleted,
|
|
CanarySteps: []rolloutv1alpha1.CanaryStepInfo{
|
|
{
|
|
CanaryStepIndex: 1,
|
|
Pods: []rolloutv1alpha1.Pod{
|
|
{
|
|
Name: "pod1",
|
|
IP: "1.2.3.1",
|
|
NodeName: "local",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
CanaryStepIndex: 2,
|
|
Pods: []rolloutv1alpha1.Pod{
|
|
{
|
|
Name: "pod2",
|
|
IP: "1.2.3.2",
|
|
NodeName: "local",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
CanaryStepIndex: 3,
|
|
Pods: []rolloutv1alpha1.Pod{
|
|
{
|
|
Name: "pod3",
|
|
IP: "1.2.3.3",
|
|
NodeName: "local",
|
|
},
|
|
{
|
|
Name: "pod4",
|
|
IP: "1.2.3.4",
|
|
NodeName: "local",
|
|
},
|
|
{
|
|
Name: "pod5",
|
|
IP: "1.2.3.5",
|
|
NodeName: "local",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return []*rolloutv1alpha1.RolloutHistory{rollouthistory}
|
|
},
|
|
},
|
|
{
|
|
name: "test5, don't create a new rolloutHistory for rollout if its phase isn't RolloutPhaseProgressing",
|
|
req: ctrl.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: "default",
|
|
Name: "rollout-demo",
|
|
},
|
|
},
|
|
getPods: func() []*corev1.Pod {
|
|
pods := []*corev1.Pod{}
|
|
return pods
|
|
},
|
|
getService: func() []*corev1.Service {
|
|
services := []*corev1.Service{}
|
|
return services
|
|
},
|
|
getWorkload: func() []*kruisev1alpha1.CloneSet {
|
|
workloads := []*kruisev1alpha1.CloneSet{}
|
|
return workloads
|
|
},
|
|
getIngress: func() []*networkingv1.Ingress {
|
|
ingresses := []*networkingv1.Ingress{}
|
|
return ingresses
|
|
},
|
|
getHTTPRoute: func() []*v1alpha2.HTTPRoute {
|
|
httpRoutes := []*v1alpha2.HTTPRoute{}
|
|
return httpRoutes
|
|
},
|
|
getRollout: func() []*rolloutv1alpha1.Rollout {
|
|
rollout := rolloutDemo1.DeepCopy()
|
|
rollout.Spec.DeprecatedRolloutID = "5"
|
|
rollout.Status = rolloutv1alpha1.RolloutStatus{
|
|
CanaryStatus: &rolloutv1alpha1.CanaryStatus{
|
|
ObservedRolloutID: "5",
|
|
},
|
|
Phase: rolloutv1alpha1.RolloutPhaseHealthy,
|
|
}
|
|
return []*rolloutv1alpha1.Rollout{rollout}
|
|
},
|
|
getRolloutHistory: func() []*rolloutv1alpha1.RolloutHistory {
|
|
rollouthistories := []*rolloutv1alpha1.RolloutHistory{}
|
|
return rollouthistories
|
|
},
|
|
expectRolloutHistory: func() []*rolloutv1alpha1.RolloutHistory {
|
|
rollouthistories := []*rolloutv1alpha1.RolloutHistory{}
|
|
return rollouthistories
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, cs := range cases {
|
|
t.Run(cs.name, func(t *testing.T) {
|
|
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
|
|
for _, obj := range cs.getPods() {
|
|
err := fakeClient.Create(context.TODO(), obj.DeepCopy(), &client.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("create Pod failed: %s", err.Error())
|
|
}
|
|
}
|
|
for _, obj := range cs.getRollout() {
|
|
err := fakeClient.Create(context.TODO(), obj.DeepCopy(), &client.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("create Rollout failed: %s", err.Error())
|
|
}
|
|
}
|
|
for _, obj := range cs.getWorkload() {
|
|
err := fakeClient.Create(context.TODO(), obj.DeepCopy(), &client.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("create Workload failed: %s", err.Error())
|
|
}
|
|
}
|
|
for _, obj := range cs.getService() {
|
|
err := fakeClient.Create(context.TODO(), obj.DeepCopy(), &client.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("create Service failed: %s", err.Error())
|
|
}
|
|
}
|
|
for _, obj := range cs.getIngress() {
|
|
err := fakeClient.Create(context.TODO(), obj.DeepCopy(), &client.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("create Ingress failed: %s", err.Error())
|
|
}
|
|
}
|
|
for _, obj := range cs.getHTTPRoute() {
|
|
err := fakeClient.Create(context.TODO(), obj.DeepCopy(), &client.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("create HTTPRoute failed: %s", err.Error())
|
|
}
|
|
}
|
|
for _, obj := range cs.getRolloutHistory() {
|
|
err := fakeClient.Create(context.TODO(), obj.DeepCopy(), &client.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("create RolloutHistory failed: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
recon := RolloutHistoryReconciler{
|
|
Client: fakeClient,
|
|
Scheme: scheme,
|
|
Finder: newControllerFinder2(fakeClient),
|
|
}
|
|
|
|
// Firstly, update spec, and requeue namespacedName
|
|
_, err := recon.Reconcile(context.TODO(), cs.req)
|
|
if err != nil {
|
|
t.Fatalf("Reconcile failed: %s", err.Error())
|
|
}
|
|
|
|
// Secondly, update spec, and requeue namespacedName
|
|
_, err = recon.Reconcile(context.TODO(), cs.req)
|
|
if err != nil {
|
|
t.Fatalf("Reconcile failed: %s", err.Error())
|
|
}
|
|
|
|
if !checkRolloutHistoryInfoEqual(fakeClient, t, cs.expectRolloutHistory()) {
|
|
t.Fatalf("Reconcile failed")
|
|
}
|
|
|
|
if !checkRolloutHistoryNum(fakeClient, t, cs.expectRolloutHistory()) {
|
|
t.Fatalf("RolloutHistory generated invalid: %s", err.Error())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func checkRolloutHistoryNum(c client.WithWatch, t *testing.T, expect []*rolloutv1alpha1.RolloutHistory) bool {
|
|
rollouthistories := &rolloutv1alpha1.RolloutHistoryList{}
|
|
err := c.List(context.TODO(), rollouthistories, &client.ListOptions{}, client.InNamespace("default"))
|
|
if err != nil {
|
|
t.Fatalf("get rollouthistories failed: %s", err.Error())
|
|
}
|
|
if len(rollouthistories.Items) != len(expect) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func checkRolloutHistoryInfoEqual(c client.WithWatch, t *testing.T, expect []*rolloutv1alpha1.RolloutHistory) bool {
|
|
for i := range expect {
|
|
obj := expect[i]
|
|
rollouthistories := &rolloutv1alpha1.RolloutHistoryList{}
|
|
err := c.List(context.TODO(), rollouthistories, &client.ListOptions{}, client.InNamespace(obj.Namespace))
|
|
if err != nil {
|
|
t.Fatalf("get rollouthistories failed: %s", err.Error())
|
|
}
|
|
// in cases, there will be just one rollouthistory
|
|
if len(rollouthistories.Items) != 1 {
|
|
t.Fatalf("create rollouthistory failed: %s", err.Error())
|
|
}
|
|
rollouthistory := rollouthistories.Items[0]
|
|
// compare Label
|
|
if !reflect.DeepEqual(obj.ObjectMeta.Labels, rollouthistory.ObjectMeta.Labels) {
|
|
t.Fatalf("diff rollouthistory label failed: %s", err.Error())
|
|
return false
|
|
}
|
|
// compare Spec
|
|
if !checkRolloutHistorySpec(&obj.Spec, &rollouthistory.Spec) {
|
|
t.Fatalf("diff rollouthistory spec failed: %s", err.Error())
|
|
return false
|
|
}
|
|
// compare status
|
|
// in the first reconcile, there is only spec updated
|
|
if !checkRolloutHistoryStatus(&obj.Status, &rollouthistory.Status) {
|
|
t.Fatalf("diff rollouthistory status failed: %s", err.Error())
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func checkRolloutHistorySpec(spec1 *rolloutv1alpha1.RolloutHistorySpec, spec2 *rolloutv1alpha1.RolloutHistorySpec) bool {
|
|
// spec1 and spec2 may be empty when rollouthistory is not completed
|
|
if reflect.DeepEqual(spec1, spec2) {
|
|
return true
|
|
}
|
|
// just compare those fields
|
|
if spec1.Rollout.Name != spec2.Rollout.Name ||
|
|
spec1.Service.Name != spec2.Service.Name ||
|
|
spec1.Workload.Name != spec2.Workload.Name ||
|
|
spec1.Rollout.RolloutID != spec2.Rollout.RolloutID ||
|
|
spec1.TrafficRouting.Ingress.Name != spec2.TrafficRouting.Ingress.Name ||
|
|
spec1.TrafficRouting.HTTPRoute.Name != spec2.TrafficRouting.HTTPRoute.Name {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func checkRolloutHistoryStatus(status1 *rolloutv1alpha1.RolloutHistoryStatus, status2 *rolloutv1alpha1.RolloutHistoryStatus) bool {
|
|
// in the first reconcile, there is only spec updated
|
|
// status1 and status2 may be empty when rollouthistory is not completed
|
|
if reflect.DeepEqual(status1, status2) {
|
|
return true
|
|
}
|
|
// just compare those fields
|
|
if status1.Phase != status2.Phase ||
|
|
len(status1.CanarySteps) != len(status2.CanarySteps) {
|
|
return false
|
|
}
|
|
// compare canarySteps, including CanaryStepIndex and pods for each canaryStep
|
|
for i := 0; i < len(status1.CanarySteps); i++ {
|
|
step1 := status1.CanarySteps[i]
|
|
step2 := status2.CanarySteps[i]
|
|
if step1.CanaryStepIndex != step2.CanaryStepIndex {
|
|
return false
|
|
}
|
|
if len(step1.Pods) != len(step2.Pods) {
|
|
return false
|
|
}
|
|
for j := 0; j < len(step1.Pods); j++ {
|
|
if step1.Pods[j].IP != step2.Pods[j].IP ||
|
|
step1.Pods[j].Name != step2.Pods[j].Name ||
|
|
step1.Pods[j].NodeName != step2.Pods[j].NodeName {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|