mirror of https://github.com/openkruise/kruise.git
2508 lines
89 KiB
Go
2508 lines
89 KiB
Go
/*
|
|
Copyright 2021 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,
|
|
WITHOWorkloadSpread 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 workloadspread
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
apps "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
"k8s.io/client-go/tools/record"
|
|
utilpointer "k8s.io/utils/pointer"
|
|
"k8s.io/utils/ptr"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
|
|
"github.com/openkruise/kruise/pkg/util"
|
|
"github.com/openkruise/kruise/pkg/util/configuration"
|
|
|
|
appsv1alpha1 "github.com/openkruise/kruise/apis/apps/v1alpha1"
|
|
"github.com/openkruise/kruise/pkg/util/controllerfinder"
|
|
"github.com/openkruise/kruise/pkg/util/fieldindex"
|
|
"github.com/openkruise/kruise/pkg/util/requeueduration"
|
|
wsutil "github.com/openkruise/kruise/pkg/util/workloadspread"
|
|
)
|
|
|
|
const (
|
|
PodDeletionCostDefault = "0"
|
|
)
|
|
|
|
var (
|
|
PodDeletionCostAnnotation = "controller.kubernetes.io/pod-deletion-cost"
|
|
scheme *runtime.Scheme
|
|
currentTime time.Time
|
|
timeFormat = "2006-01-02 15:04:05"
|
|
m, _ = time.ParseDuration("-1m")
|
|
s, _ = time.ParseDuration("-1s")
|
|
|
|
podDemo = &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-pod-0",
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app": "nginx",
|
|
},
|
|
Annotations: map[string]string{},
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "apps.kruise.io/v1alpha1",
|
|
Kind: "CloneSet",
|
|
Name: "cloneset-test",
|
|
Controller: ptr.To(true),
|
|
UID: types.UID("a03eb001-27eb-4713-b634-7c46f6861758"),
|
|
BlockOwnerDeletion: ptr.To(true),
|
|
},
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
NodeName: "node-1",
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "nginx",
|
|
Image: "nginx:1.15.1",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
cloneSetDemo = &appsv1alpha1.CloneSet{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "CloneSet",
|
|
APIVersion: "apps.kruise.io/v1alpha1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "cloneset-test",
|
|
Namespace: "default",
|
|
Generation: 10,
|
|
UID: types.UID("a03eb001-27eb-4713-b634-7c46f6861758"),
|
|
},
|
|
Spec: appsv1alpha1.CloneSetSpec{
|
|
Replicas: ptr.To(int32(10)),
|
|
Selector: &metav1.LabelSelector{
|
|
MatchLabels: map[string]string{
|
|
"app": "nginx",
|
|
},
|
|
},
|
|
},
|
|
Status: appsv1alpha1.CloneSetStatus{
|
|
ObservedGeneration: 10,
|
|
UpdateRevision: wsutil.VersionIgnored,
|
|
},
|
|
}
|
|
|
|
workloadSpreadDemo = &appsv1alpha1.WorkloadSpread{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: "apps.kruise.io/v1alpha1",
|
|
Kind: "WorkloadSpread",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-workloadSpread",
|
|
Namespace: "default",
|
|
Generation: 10,
|
|
},
|
|
Spec: appsv1alpha1.WorkloadSpreadSpec{
|
|
TargetReference: &appsv1alpha1.TargetReference{
|
|
APIVersion: "apps.kruise.io/v1alpha1",
|
|
Kind: "CloneSet",
|
|
Name: "cloneset-test",
|
|
},
|
|
Subsets: []appsv1alpha1.WorkloadSpreadSubset{
|
|
{
|
|
Name: "subset-a",
|
|
MaxReplicas: &intstr.IntOrString{Type: intstr.Int, IntVal: 5},
|
|
},
|
|
},
|
|
},
|
|
Status: appsv1alpha1.WorkloadSpreadStatus{
|
|
ObservedGeneration: 10,
|
|
SubsetStatuses: []appsv1alpha1.WorkloadSpreadSubsetStatus{
|
|
{
|
|
Name: "subset-a",
|
|
MissingReplicas: 5,
|
|
CreatingPods: map[string]metav1.Time{},
|
|
DeletingPods: map[string]metav1.Time{},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
subsetDemo = appsv1alpha1.WorkloadSpreadSubset{
|
|
Name: "subset-a",
|
|
MaxReplicas: &intstr.IntOrString{Type: intstr.Int, IntVal: 5},
|
|
}
|
|
|
|
getWhiteListDemoCopy = func() configuration.WSCustomWorkloadWhiteList {
|
|
return configuration.WSCustomWorkloadWhiteList{
|
|
Workloads: []configuration.CustomWorkload{
|
|
{
|
|
GroupVersionKind: schema.GroupVersionKind{
|
|
Group: "mock.kruise.io",
|
|
Version: "v1",
|
|
Kind: "GameServerSet",
|
|
},
|
|
ReplicasPath: "spec.replicas",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
scheme = runtime.NewScheme()
|
|
utilruntime.Must(corev1.AddToScheme(scheme))
|
|
utilruntime.Must(appsv1alpha1.AddToScheme(scheme))
|
|
}
|
|
|
|
func TestSubsetPodDeletionCost(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
subsetIndex int
|
|
getPods func() []*corev1.Pod
|
|
getWorkloadSpread func() *appsv1alpha1.WorkloadSpread
|
|
expectPods func() []*corev1.Pod
|
|
}{
|
|
{
|
|
name: "pods number == maxReplicas, subsetsLen = 2, subsetIndex = 0, maxReplicas is 3, pods number is 3",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 3)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets = make([]appsv1alpha1.WorkloadSpreadSubset, 2)
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 3}
|
|
workloadSpread.Spec.Subsets[1].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 3}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 3)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "200",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "pods number == maxReplicas, subsetsLen = 2, subsetIndex = 1, maxReplicas is 3, pods number is 3",
|
|
subsetIndex: 1,
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 3)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets = make([]appsv1alpha1.WorkloadSpreadSubset, 2)
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 3}
|
|
workloadSpread.Spec.Subsets[1].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 3}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 3)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "pods number == maxReplicas, subsetsLen = 2, subsetIndex = 0, maxReplicas is 3, pods number is 4",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 4)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets = make([]appsv1alpha1.WorkloadSpreadSubset, 2)
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 3}
|
|
workloadSpread.Spec.Subsets[1].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 3}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 4)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "200",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[0].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "pods number == maxReplicas, subsetsLen = 2, subsetIndex = 1, maxReplicas is 3, pods number is 4",
|
|
subsetIndex: 1,
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 4)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets = make([]appsv1alpha1.WorkloadSpreadSubset, 2)
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 3}
|
|
workloadSpread.Spec.Subsets[1].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 3}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 4)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[0].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-200",
|
|
}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "pods number == maxReplicas, maxReplicas is 3, pods number is 3",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 3)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 3}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 3)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "pods number < maxReplicas, maxReplicas is 3, pods number is 2",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 2)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 3}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 2)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "active pods number > maxReplicas, maxReplicas is 1, pods number is 3",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 3)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 1}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 3)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[2].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "active pods number > maxReplicas, maxReplicas is 1, pods number is 4, pod-0 deletionTimestamp is not nil",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 4)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
deleteTime := metav1.Now()
|
|
pods[0].DeletionTimestamp = &deleteTime
|
|
pods[0].Finalizers = []string{"finalizers.sigs.k8s.io/test"}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 1}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 4)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[0].Annotations = map[string]string{}
|
|
pods[3].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "maxReplicas is nil, pods number is 3, original annotation == 1",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 3)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations[PodDeletionCostAnnotation] = PodDeletionCostDefault
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = nil
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 3)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations[PodDeletionCostAnnotation] = "100"
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "maxReplicas is 2, active pods number is 5, (pod-0, pod-3, pod-4) are unhealthy",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[0].Spec.NodeName = ""
|
|
pods[1].Status.Phase = corev1.PodRunning
|
|
pods[2].Status.Phase = corev1.PodRunning
|
|
pods[3].Status.Phase = corev1.PodPending
|
|
pods[4].Status.Phase = corev1.PodPending
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 2}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[0].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[3].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[4].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "maxReplicas is 2, active pods number is 5, (pod-2, pod-3, pod-4) are unhealthy",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[0].Status.Phase = corev1.PodRunning
|
|
pods[1].Status.Phase = corev1.PodRunning
|
|
|
|
pods[2].Spec.NodeName = ""
|
|
pods[3].Status.Phase = corev1.PodPending
|
|
pods[4].Status.Phase = corev1.PodUnknown
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 2}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[2].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[3].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[4].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "maxReplicas is 2, active pods number is 5, (pod-2, pod-3) are unhealthy, pod-4 deletionTimestamp is not nil",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[0].Status.Phase = corev1.PodRunning
|
|
pods[1].Status.Phase = corev1.PodRunning
|
|
|
|
pods[2].Spec.NodeName = ""
|
|
pods[3].Status.Phase = corev1.PodPending
|
|
|
|
deleteTime := metav1.Now()
|
|
pods[4].DeletionTimestamp = &deleteTime
|
|
pods[4].Finalizers = []string{"finalizers.sigs.k8s.io/test"}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 2}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[2].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[3].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[4].Annotations = map[string]string{}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "maxReplicas is 2, active pods number is 5, (pod-2, pod-4) are unhealthy, pod-3 deletionTimestamp is not nil",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[0].Status.Phase = corev1.PodRunning
|
|
pods[1].Status.Phase = corev1.PodRunning
|
|
|
|
pods[2].Spec.NodeName = ""
|
|
pods[4].Status.Phase = corev1.PodPending
|
|
|
|
deleteTime := metav1.Now()
|
|
pods[3].DeletionTimestamp = &deleteTime
|
|
pods[3].Finalizers = []string{"finalizers.sigs.k8s.io/test"}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 2}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[2].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[4].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[3].Annotations = map[string]string{}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "maxReplicas is 2, active pods number is 4, (pod-2, pod-4) are unhealthy, pod-3 is failed",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[0].Status.Phase = corev1.PodRunning
|
|
pods[1].Status.Phase = corev1.PodRunning
|
|
|
|
pods[2].Spec.NodeName = ""
|
|
pods[4].Status.Phase = corev1.PodPending
|
|
pods[3].Status.Phase = corev1.PodFailed
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 2}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[2].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[4].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[3].Annotations = map[string]string{}
|
|
return pods
|
|
},
|
|
},
|
|
{
|
|
name: "maxReplicas is 2, active pods number is 4, (pod-2, pod-4) are unhealthy, pod-3 is succeed",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[0].Status.Phase = corev1.PodRunning
|
|
pods[1].Status.Phase = corev1.PodRunning
|
|
|
|
pods[2].Spec.NodeName = ""
|
|
pods[4].Status.Phase = corev1.PodPending
|
|
pods[3].Status.Phase = corev1.PodSucceeded
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 2}
|
|
return workloadSpread
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
}
|
|
pods[2].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[4].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "-100",
|
|
}
|
|
pods[3].Annotations = map[string]string{}
|
|
return pods
|
|
},
|
|
},
|
|
}
|
|
for _, cs := range cases {
|
|
t.Run(cs.name, func(t *testing.T) {
|
|
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
|
|
for _, pod := range cs.getPods() {
|
|
podIn := pod.DeepCopy()
|
|
err := fakeClient.Create(context.TODO(), podIn)
|
|
if err != nil {
|
|
t.Fatalf("create pod failed: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
workloadSpread := cs.getWorkloadSpread()
|
|
|
|
r := ReconcileWorkloadSpread{
|
|
Client: fakeClient,
|
|
recorder: record.NewFakeRecorder(10),
|
|
}
|
|
|
|
err := r.syncSubsetPodDeletionCost(workloadSpread, &workloadSpread.Spec.Subsets[0], cs.subsetIndex, cs.getPods(), 5)
|
|
if err != nil {
|
|
t.Fatalf("set pod deletion-cost annotation failed: %s", err.Error())
|
|
}
|
|
|
|
latestPods, _ := getLatestPods(fakeClient, workloadSpread)
|
|
expectPods := cs.expectPods()
|
|
|
|
if len(latestPods) != len(expectPods) {
|
|
t.Fatalf("set Pod deletion-coset annotation failed")
|
|
}
|
|
for i := range expectPods {
|
|
annotation1 := latestPods[i].Annotations
|
|
annotation2 := expectPods[i].Annotations
|
|
if !apiequality.Semantic.DeepEqual(annotation1, annotation2) {
|
|
fmt.Println(expectPods[i].Name)
|
|
fmt.Println(annotation1)
|
|
fmt.Println(annotation2)
|
|
t.Fatalf("set Pod deletion-coset annotation failed")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkloadSpreadReconcile(t *testing.T) {
|
|
getTwoPodsWithDifferentLabels := func() []*corev1.Pod {
|
|
pod1 := podDemo.DeepCopy()
|
|
pod1.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
pod1.OwnerReferences = []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "mock.kruise.io/v1",
|
|
Kind: "GameServerSet",
|
|
Name: "workload",
|
|
UID: "12345",
|
|
},
|
|
}
|
|
pod1.Labels["selected"] = "true"
|
|
pod2 := pod1.DeepCopy()
|
|
pod2.Name = "another"
|
|
pod2.Labels["selected"] = "false"
|
|
pod2.Labels["app"] = "not-nginx" // preventing being selected by func getLatestPods
|
|
return []*corev1.Pod{pod1, pod2}
|
|
}
|
|
|
|
getWorkloadSpreadWithPercentSubsetB := func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets = append(workloadSpread.Spec.Subsets, appsv1alpha1.WorkloadSpreadSubset{
|
|
Name: "subset-b",
|
|
MaxReplicas: &intstr.IntOrString{Type: intstr.String, StrVal: "50%"},
|
|
})
|
|
workloadSpread.Spec.TargetReference = &appsv1alpha1.TargetReference{
|
|
APIVersion: "mock.kruise.io/v1",
|
|
Kind: "GameServerSet",
|
|
Name: "workload",
|
|
}
|
|
workloadSpread.Spec.TargetFilter = &appsv1alpha1.TargetFilter{
|
|
Selector: &metav1.LabelSelector{
|
|
MatchLabels: map[string]string{
|
|
"selected": "true",
|
|
},
|
|
},
|
|
}
|
|
return workloadSpread
|
|
}
|
|
expectWorkloadSpreadWithPercentSubsetB := func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.TargetReference = &appsv1alpha1.TargetReference{
|
|
APIVersion: "mock.kruise.io/v1",
|
|
Kind: "GameServerSet",
|
|
Name: "workload",
|
|
}
|
|
workloadSpread.Status.SubsetStatuses = append(workloadSpread.Status.SubsetStatuses, appsv1alpha1.WorkloadSpreadSubsetStatus{})
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 4
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 1
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[1].Name = "subset-b"
|
|
workloadSpread.Status.SubsetStatuses[1].MissingReplicas = 0
|
|
workloadSpread.Status.SubsetStatuses[1].Replicas = 0
|
|
workloadSpread.Status.SubsetStatuses[1].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[1].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
}
|
|
cases := []struct {
|
|
name string
|
|
getPods func() []*corev1.Pod
|
|
getWorkloadSpread func() *appsv1alpha1.WorkloadSpread
|
|
getCloneSet func() *appsv1alpha1.CloneSet
|
|
getWorkloads func() []client.Object
|
|
getWhiteList func() configuration.WSCustomWorkloadWhiteList
|
|
expectPods func() []*corev1.Pod
|
|
expectWorkloadSpread func() *appsv1alpha1.WorkloadSpread
|
|
}{
|
|
{
|
|
name: "one subset, create zero pod, maxReplicas = 5, missingReplicas = 5",
|
|
getPods: func() []*corev1.Pod {
|
|
return []*corev1.Pod{}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
return workloadSpreadDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
return []*corev1.Pod{}
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.ObservedGeneration = 10
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 5
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "two subset, create zero pod, maxReplicas = 5, missingReplicas = 5",
|
|
getPods: func() []*corev1.Pod {
|
|
return []*corev1.Pod{}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
subset1 := subsetDemo.DeepCopy()
|
|
subset2 := subsetDemo.DeepCopy()
|
|
subset2.Name = "subset-b"
|
|
workloadSpread.Spec.Subsets = []appsv1alpha1.WorkloadSpreadSubset{*subset1, *subset2}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
return []*corev1.Pod{}
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.ObservedGeneration = 10
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(10)
|
|
workloadSpread.Status.SubsetStatuses = make([]appsv1alpha1.WorkloadSpreadSubsetStatus, 2)
|
|
workloadSpread.Status.SubsetStatuses[0].Name = "subset-a"
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 5
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[1].Name = "subset-b"
|
|
workloadSpread.Status.SubsetStatuses[1].MissingReplicas = 5
|
|
workloadSpread.Status.SubsetStatuses[1].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[1].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "two subset, create two pod, maxReplicas = 5, missingReplicas = 4",
|
|
getPods: func() []*corev1.Pod {
|
|
pod1 := podDemo.DeepCopy()
|
|
pod1.Name = "test-pod-0"
|
|
pod1.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
pod2 := podDemo.DeepCopy()
|
|
pod2.Name = "test-pod-1"
|
|
pod2.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-b"}`,
|
|
}
|
|
return []*corev1.Pod{pod1, pod2}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
subset1 := subsetDemo.DeepCopy()
|
|
subset2 := subsetDemo.DeepCopy()
|
|
subset2.Name = "subset-b"
|
|
workloadSpread.Spec.Subsets = []appsv1alpha1.WorkloadSpreadSubset{*subset1, *subset2}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pod1 := podDemo.DeepCopy()
|
|
pod1.Name = "test-pod-0"
|
|
pod1.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
PodDeletionCostAnnotation: "200",
|
|
}
|
|
pod2 := podDemo.DeepCopy()
|
|
pod2.Name = "test-pod-1"
|
|
pod2.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-b"}`,
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
return []*corev1.Pod{pod1, pod2}
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.ObservedGeneration = 10
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(10)
|
|
workloadSpread.Status.SubsetStatuses = make([]appsv1alpha1.WorkloadSpreadSubsetStatus, 2)
|
|
workloadSpread.Status.SubsetStatuses[0].Name = "subset-a"
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 4
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 1
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[1].Name = "subset-b"
|
|
workloadSpread.Status.SubsetStatuses[1].MissingReplicas = 4
|
|
workloadSpread.Status.SubsetStatuses[1].Replicas = 1
|
|
workloadSpread.Status.SubsetStatuses[1].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[1].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "create one pod, maxReplicas = 5, missingReplicas = 4",
|
|
getPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
return workloadSpreadDemo.DeepCopy()
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(10)
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 4
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 1
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "create one pod, maxReplicas = 100%, missingReplicas = 4",
|
|
getPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.String, StrVal: "100%"}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
cloneSet := cloneSetDemo.DeepCopy()
|
|
cloneSet.Spec.Replicas = ptr.To(int32(5))
|
|
return cloneSet
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(5)
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 4
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 1
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "create one pod, maxReplicas = 80%, missingReplicas = 3",
|
|
getPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.String, StrVal: "80%"}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
cloneSet := cloneSetDemo.DeepCopy()
|
|
cloneSet.Spec.Replicas = ptr.To(int32(5))
|
|
return cloneSet
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(5)
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 3
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 1
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "create two pods, one failed but no timeout, maxReplicas = 5, missingReplicas = 3",
|
|
getPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-0": {Time: currentTime.Add(2 * s)},
|
|
"test-pod-1": {Time: currentTime.Add(2 * s)},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses[0].Name = "subset-a"
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
formatTime := currentTime.Format(timeFormat)
|
|
currentTime, _ = time.ParseInLocation(timeFormat, formatTime, time.Local)
|
|
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(10)
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 3
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 1
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-1": {Time: currentTime.Add(2 * s)},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "create two pod, one failed and timeout, maxReplicas = 5, missingReplicas = 4",
|
|
getPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-0": {Time: currentTime.Add(1 * m)},
|
|
"test-pod-1": {Time: currentTime.Add(1 * m)},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses[0].Name = "subset-a"
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(10)
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 4
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 1
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].Name = "subset-a"
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "create two pods, two succeed, maxReplicas = 5, missingReplicas = 3",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 2)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-0": {Time: currentTime.Add(3 * s)},
|
|
"test-pod-1": {Time: currentTime.Add(3 * s)},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 2)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
}
|
|
return pods
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(10)
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 3
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 2
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "create five pods, two succeed, maxReplicas = 5, missingReplicas = 3",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
pods[2].Status.Phase = corev1.PodFailed
|
|
pods[3].Status.Phase = corev1.PodFailed
|
|
pods[4].Status.Phase = corev1.PodSucceeded
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-0": {Time: currentTime.Add(3 * s)},
|
|
"test-pod-1": {Time: currentTime.Add(3 * s)},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
pods[0].Annotations[PodDeletionCostAnnotation] = "100"
|
|
pods[1].Annotations[PodDeletionCostAnnotation] = "100"
|
|
return pods
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(10)
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 3
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 2
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "create five pods, all succeed, maxReplicas = 5, missingReplicas = 0",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-3": {Time: currentTime.Add(5 * s)},
|
|
"test-pod-4": {Time: currentTime.Add(5 * s)},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
}
|
|
return pods
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(10)
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 0
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 5
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "create five pods, all succeed and remove one, maxReplicas = 5, missingReplicas = 1",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
deleteTime := metav1.Time{Time: currentTime.Add(1 * s)}
|
|
pods[4].DeletionTimestamp = &deleteTime
|
|
pods[4].Finalizers = []string{"finalizers.sigs.k8s.io/test"}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-3": {Time: currentTime.Add(5 * s)},
|
|
"test-pod-4": {Time: currentTime.Add(5 * s)},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{
|
|
"test-pod-4": {Time: currentTime.Add(1 * s)},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
}
|
|
pods[4].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
return pods
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(10)
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 1
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 4
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "create five pods, all succeed and remove one failed but no timeout, maxReplicas = 5, missingReplicas = 1",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-3": {Time: currentTime.Add(5 * s)},
|
|
"test-pod-4": {Time: currentTime.Add(5 * s)},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{
|
|
"test-pod-4": {Time: currentTime.Add(1 * s)},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
}
|
|
return pods
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(10)
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 1
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 5
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
|
|
formatTime := currentTime.Format(timeFormat)
|
|
currentTime, _ = time.ParseInLocation(timeFormat, formatTime, time.Local)
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{
|
|
"test-pod-4": {Time: currentTime.Add(1 * s)},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "create five pods, all succeed, remove one failed and timeout, maxReplicas = 5, missingReplicas = 0",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-3": {Time: currentTime.Add(6 * m)},
|
|
"test-pod-4": {Time: currentTime.Add(6 * m)},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{
|
|
"test-pod-4": {Time: currentTime.Add(16 * m)},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
}
|
|
return pods
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(10)
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 0
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 5
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "multiVersion pods",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 10)
|
|
for i := 0; i < 6; i++ {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
for i := 6; i < 10; i++ {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-b"}`,
|
|
}
|
|
}
|
|
pods[0].Labels[apps.DefaultDeploymentUniqueLabelKey] = "oldVersion"
|
|
pods[2].Labels[apps.DefaultDeploymentUniqueLabelKey] = "oldVersion"
|
|
pods[4].Labels[apps.DefaultDeploymentUniqueLabelKey] = "oldVersion"
|
|
pods[6].Labels[apps.DefaultDeploymentUniqueLabelKey] = "oldVersion"
|
|
pods[8].Labels[apps.DefaultDeploymentUniqueLabelKey] = "oldVersion"
|
|
pods[1].Labels[apps.DefaultDeploymentUniqueLabelKey] = "newVersion"
|
|
pods[3].Labels[apps.DefaultDeploymentUniqueLabelKey] = "newVersion"
|
|
pods[5].Labels[apps.DefaultDeploymentUniqueLabelKey] = "newVersion"
|
|
pods[7].Labels[apps.DefaultDeploymentUniqueLabelKey] = "newVersion"
|
|
pods[9].Labels[apps.DefaultDeploymentUniqueLabelKey] = "newVersion"
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets = []appsv1alpha1.WorkloadSpreadSubset{
|
|
{
|
|
Name: "subset-a",
|
|
MaxReplicas: &intstr.IntOrString{Type: intstr.Int, IntVal: 3},
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
clone := cloneSetDemo.DeepCopy()
|
|
clone.Status.UpdateRevision = "newVersion"
|
|
return clone
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 10)
|
|
for i := 0; i < 6; i++ {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
for i := 6; i < 10; i++ {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-b"}`,
|
|
}
|
|
}
|
|
pods[0].Annotations[PodDeletionCostAnnotation] = "100"
|
|
pods[0].Labels[apps.DefaultDeploymentUniqueLabelKey] = "oldVersion"
|
|
pods[2].Annotations[PodDeletionCostAnnotation] = "100"
|
|
pods[2].Labels[apps.DefaultDeploymentUniqueLabelKey] = "oldVersion"
|
|
pods[4].Annotations[PodDeletionCostAnnotation] = "100"
|
|
pods[4].Labels[apps.DefaultDeploymentUniqueLabelKey] = "oldVersion"
|
|
pods[6].Annotations[PodDeletionCostAnnotation] = "200"
|
|
pods[6].Labels[apps.DefaultDeploymentUniqueLabelKey] = "oldVersion"
|
|
pods[8].Annotations[PodDeletionCostAnnotation] = "200"
|
|
pods[8].Labels[apps.DefaultDeploymentUniqueLabelKey] = "oldVersion"
|
|
|
|
pods[1].Annotations[PodDeletionCostAnnotation] = "200"
|
|
pods[1].Labels[apps.DefaultDeploymentUniqueLabelKey] = "newVersion"
|
|
pods[3].Annotations[PodDeletionCostAnnotation] = "200"
|
|
pods[3].Labels[apps.DefaultDeploymentUniqueLabelKey] = "newVersion"
|
|
pods[5].Annotations[PodDeletionCostAnnotation] = "200"
|
|
pods[5].Labels[apps.DefaultDeploymentUniqueLabelKey] = "newVersion"
|
|
pods[7].Annotations[PodDeletionCostAnnotation] = "100"
|
|
pods[7].Labels[apps.DefaultDeploymentUniqueLabelKey] = "newVersion"
|
|
pods[9].Annotations[PodDeletionCostAnnotation] = "100"
|
|
pods[9].Labels[apps.DefaultDeploymentUniqueLabelKey] = "newVersion"
|
|
return pods
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.SubsetStatuses = make([]appsv1alpha1.WorkloadSpreadSubsetStatus, 2)
|
|
workloadSpread.Status.SubsetStatuses[0].Name = "subset-a"
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 0
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 6
|
|
workloadSpread.Status.SubsetStatuses[1].Name = "subset-b"
|
|
workloadSpread.Status.SubsetStatuses[1].MissingReplicas = -1
|
|
workloadSpread.Status.SubsetStatuses[1].Replicas = 4
|
|
|
|
workloadSpread.Status.VersionedSubsetStatuses = map[string][]appsv1alpha1.WorkloadSpreadSubsetStatus{
|
|
"oldVersion": {
|
|
{
|
|
Name: "subset-a",
|
|
MissingReplicas: 0,
|
|
Replicas: 3,
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
MissingReplicas: -1,
|
|
Replicas: 2,
|
|
},
|
|
},
|
|
"newVersion": {
|
|
{
|
|
Name: "subset-a",
|
|
MissingReplicas: 0,
|
|
Replicas: 3,
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
MissingReplicas: -1,
|
|
Replicas: 2,
|
|
},
|
|
},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "create five pods, all succeed, maxReplicas = 3, missingReplicas = 0, pod-0, pod-1 deletion-cost = -100",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &intstr.IntOrString{Type: intstr.Int, IntVal: 3}
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-3": {Time: currentTime.Add(6 * m)},
|
|
"test-pod-4": {Time: currentTime.Add(6 * m)},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pods-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
PodDeletionCostAnnotation: "100",
|
|
}
|
|
}
|
|
pods[0].Annotations[PodDeletionCostAnnotation] = "-100"
|
|
pods[1].Annotations[PodDeletionCostAnnotation] = "-100"
|
|
return pods
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
//workloadSpread.Status.ObservedWorkloadReplicas = int32(10)
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 0
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 5
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{}
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "custom workload with replica path in whitelist",
|
|
getWorkloads: func() []client.Object {
|
|
clone := cloneSetDemo.DeepCopy()
|
|
clone.Name = "workload"
|
|
clone.Kind = "GameServerSet"
|
|
clone.APIVersion = "mock.kruise.io/v1"
|
|
clone.UID = "12345"
|
|
clone.Spec.Replicas = utilpointer.Int32(14)
|
|
unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(clone)
|
|
if err != nil {
|
|
panic("err when convert to unstructured object")
|
|
}
|
|
return []client.Object{&unstructured.Unstructured{Object: unstructuredMap}}
|
|
},
|
|
getPods: getTwoPodsWithDifferentLabels,
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := getWorkloadSpreadWithPercentSubsetB()
|
|
workloadSpread.Spec.TargetFilter = nil
|
|
return workloadSpread
|
|
},
|
|
getWhiteList: getWhiteListDemoCopy,
|
|
expectPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := expectWorkloadSpreadWithPercentSubsetB()
|
|
workloadSpread.Status.SubsetStatuses[0].Replicas = 2
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 3
|
|
workloadSpread.Status.SubsetStatuses[1].Replicas = 0
|
|
workloadSpread.Status.SubsetStatuses[1].MissingReplicas = 7
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "custom workload with target filter",
|
|
getWorkloads: func() []client.Object {
|
|
clone := cloneSetDemo.DeepCopy()
|
|
clone.Name = "workload"
|
|
clone.Kind = "GameServerSet"
|
|
clone.APIVersion = "mock.kruise.io/v1"
|
|
clone.UID = "12345"
|
|
clone.Spec.RevisionHistoryLimit = utilpointer.Int32(18) // as replicas
|
|
unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(clone)
|
|
if err != nil {
|
|
panic("err when convert to unstructured object")
|
|
}
|
|
return []client.Object{&unstructured.Unstructured{Object: unstructuredMap}}
|
|
},
|
|
getPods: func() []*corev1.Pod {
|
|
pod1 := podDemo.DeepCopy()
|
|
pod1.OwnerReferences = []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "mock.kruise.io/v1",
|
|
Kind: "GameServerSet",
|
|
Name: "workload",
|
|
UID: "12345",
|
|
},
|
|
}
|
|
pod1.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
pod2 := pod1.DeepCopy()
|
|
pod1.Labels["selected"] = "true"
|
|
pod2.Labels["selected"] = "false"
|
|
return []*corev1.Pod{pod1}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := getWorkloadSpreadWithPercentSubsetB()
|
|
workloadSpread.Spec.TargetFilter.ReplicasPathList = []string{"spec.revisionHistoryLimit"}
|
|
return workloadSpread
|
|
},
|
|
getWhiteList: getWhiteListDemoCopy,
|
|
expectPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
expectWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := expectWorkloadSpreadWithPercentSubsetB()
|
|
workloadSpread.Status.SubsetStatuses[1].MissingReplicas = 9
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "custom workload without replicas",
|
|
getWorkloads: func() []client.Object {
|
|
clone := cloneSetDemo.DeepCopy()
|
|
clone.Name = "workload"
|
|
clone.Kind = "GameServerSet"
|
|
clone.APIVersion = "mock.kruise.io/v1"
|
|
clone.UID = "12345"
|
|
// with no any replicas
|
|
unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(clone)
|
|
if err != nil {
|
|
panic("err when convert to unstructured object")
|
|
}
|
|
return []client.Object{&unstructured.Unstructured{Object: unstructuredMap}}
|
|
},
|
|
getPods: getTwoPodsWithDifferentLabels,
|
|
getWorkloadSpread: getWorkloadSpreadWithPercentSubsetB,
|
|
getWhiteList: func() configuration.WSCustomWorkloadWhiteList {
|
|
whiteList := getWhiteListDemoCopy()
|
|
whiteList.Workloads[0].ReplicasPath = "" // not configured
|
|
return whiteList
|
|
},
|
|
expectPods: func() []*corev1.Pod {
|
|
pod := podDemo.DeepCopy()
|
|
pod.Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
return []*corev1.Pod{pod}
|
|
},
|
|
expectWorkloadSpread: expectWorkloadSpreadWithPercentSubsetB,
|
|
},
|
|
}
|
|
if !wsutil.EnabledWorkloadSetForVersionedStatus.Has("cloneset") {
|
|
wsutil.EnabledWorkloadSetForVersionedStatus.Insert("cloneset")
|
|
defer wsutil.EnabledWorkloadSetForVersionedStatus.Delete("cloneset")
|
|
}
|
|
|
|
for _, cs := range cases {
|
|
t.Run(cs.name, func(t *testing.T) {
|
|
currentTime = time.Now()
|
|
workloadSpread := cs.getWorkloadSpread()
|
|
builder := fake.NewClientBuilder().WithScheme(scheme).WithObjects(workloadSpread).
|
|
WithIndex(&corev1.Pod{}, fieldindex.IndexNameForOwnerRefUID, func(obj client.Object) []string {
|
|
var owners []string
|
|
for _, ref := range obj.GetOwnerReferences() {
|
|
owners = append(owners, string(ref.UID))
|
|
}
|
|
return owners
|
|
}).WithStatusSubresource(&appsv1alpha1.WorkloadSpread{})
|
|
if cs.getCloneSet != nil {
|
|
builder.WithObjects(cs.getCloneSet())
|
|
}
|
|
if cs.getWorkloads != nil {
|
|
builder.WithObjects(cs.getWorkloads()...)
|
|
}
|
|
if cs.getWhiteList != nil {
|
|
whiteList := cs.getWhiteList()
|
|
marshaled, _ := json.Marshal(whiteList)
|
|
builder.WithObjects(&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: configuration.KruiseConfigurationName,
|
|
Namespace: util.GetKruiseNamespace(),
|
|
},
|
|
Data: map[string]string{
|
|
configuration.WSWatchCustomWorkloadWhiteList: string(marshaled),
|
|
},
|
|
})
|
|
}
|
|
for _, pod := range cs.getPods() {
|
|
podIn := pod.DeepCopy()
|
|
builder.WithObjects(podIn)
|
|
}
|
|
fakeClient := builder.Build()
|
|
|
|
reconciler := ReconcileWorkloadSpread{
|
|
Client: fakeClient,
|
|
recorder: record.NewFakeRecorder(10),
|
|
controllerFinder: &controllerfinder.ControllerFinder{Client: fakeClient},
|
|
}
|
|
|
|
err := reconciler.syncWorkloadSpread(workloadSpread)
|
|
if err != nil {
|
|
t.Fatalf("sync WorkloadSpread failed: %s", err.Error())
|
|
}
|
|
|
|
latestPodList, _ := getLatestPods(fakeClient, workloadSpread)
|
|
expectPodList := cs.expectPods()
|
|
|
|
for i := range latestPodList {
|
|
annotation1 := latestPodList[i].Annotations
|
|
annotation2 := expectPodList[i].Annotations
|
|
if !reflect.DeepEqual(annotation1, annotation2) {
|
|
fmt.Println(annotation1)
|
|
fmt.Println(annotation2)
|
|
t.Fatalf("set Pod %v deletion-cost annotation failed", latestPodList[i].Name)
|
|
}
|
|
}
|
|
|
|
latestWorkloadSpread, err := getLatestWorkloadSpread(fakeClient, workloadSpread)
|
|
if err != nil {
|
|
t.Fatalf("getLatestWorkloadSpread failed: %s", err.Error())
|
|
}
|
|
|
|
latestStatus := latestWorkloadSpread.Status.SubsetStatuses
|
|
by, _ := json.Marshal(latestStatus)
|
|
fmt.Println(string(by))
|
|
|
|
exceptStatus := cs.expectWorkloadSpread().Status.SubsetStatuses
|
|
by, _ = json.Marshal(exceptStatus)
|
|
fmt.Println(string(by))
|
|
|
|
if !apiequality.Semantic.DeepEqual(latestStatus, exceptStatus) {
|
|
t.Fatalf("workloadSpread status DeepEqual failed")
|
|
}
|
|
|
|
if len(latestWorkloadSpread.Status.VersionedSubsetStatuses) > 1 {
|
|
latestVersionedStatus := latestWorkloadSpread.Status.VersionedSubsetStatuses
|
|
by, _ := json.Marshal(latestVersionedStatus)
|
|
fmt.Println(string(by))
|
|
|
|
exceptVersionedStatus := cs.expectWorkloadSpread().Status.VersionedSubsetStatuses
|
|
by, _ = json.Marshal(exceptVersionedStatus)
|
|
fmt.Println(string(by))
|
|
|
|
if !apiequality.Semantic.DeepEqual(latestVersionedStatus, exceptVersionedStatus) {
|
|
t.Fatalf("workloadSpread versioned status DeepEqual failed")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// This test checks that user changes subsets sequence of WorkloadSpreadSpec.
|
|
func TestUpdateSubsetSequence(t *testing.T) {
|
|
pods := make([]*corev1.Pod, 2)
|
|
pods[0] = podDemo.DeepCopy()
|
|
pods[0].Name = "test-pod-0"
|
|
pods[0].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
pods[1] = podDemo.DeepCopy()
|
|
pods[1].Name = "test-pod-1"
|
|
pods[1].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-b"}`,
|
|
}
|
|
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
subset1 := subsetDemo.DeepCopy()
|
|
subset2 := subsetDemo.DeepCopy()
|
|
subset2.Name = "subset-b"
|
|
workloadSpread.Spec.Subsets = []appsv1alpha1.WorkloadSpreadSubset{*subset2, *subset1}
|
|
|
|
workloadSpread.Status.SubsetStatuses = make([]appsv1alpha1.WorkloadSpreadSubsetStatus, 2)
|
|
workloadSpread.Status.SubsetStatuses[0].Name = "subset-a"
|
|
workloadSpread.Status.SubsetStatuses[0].MissingReplicas = 4
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-0": {Time: currentTime.Add(2 * s)},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses[1].Name = "subset-b"
|
|
workloadSpread.Status.SubsetStatuses[1].MissingReplicas = 4
|
|
workloadSpread.Status.SubsetStatuses[1].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-1": {Time: currentTime.Add(2 * s)},
|
|
}
|
|
|
|
r := ReconcileWorkloadSpread{}
|
|
versionedPodMap, subsetsPods, err := r.groupVersionedPods(workloadSpread, pods, 5)
|
|
if err != nil {
|
|
t.Fatalf("error group pods")
|
|
}
|
|
status, _ := r.calculateWorkloadSpreadStatus(workloadSpread, versionedPodMap, subsetsPods, 5)
|
|
if status == nil {
|
|
t.Fatalf("error get WorkloadSpread status")
|
|
} else {
|
|
if status.SubsetStatuses[0].Name != subset2.Name || status.SubsetStatuses[1].Name != subset1.Name {
|
|
t.Fatalf("WorkloadSpread's status mismatch subsets of spec")
|
|
}
|
|
if status.SubsetStatuses[0].MissingReplicas != 4 ||
|
|
!apiequality.Semantic.DeepEqual(status.SubsetStatuses[0].CreatingPods, map[string]metav1.Time{}) {
|
|
t.Fatalf("failted to sync WorkloadSpread subset-0 status")
|
|
}
|
|
if status.SubsetStatuses[1].MissingReplicas != 4 ||
|
|
!apiequality.Semantic.DeepEqual(status.SubsetStatuses[1].CreatingPods, map[string]metav1.Time{}) {
|
|
t.Fatalf("failted to sync WorkloadSpread subset-1 status")
|
|
}
|
|
}
|
|
}
|
|
|
|
// This test checks that some creation or deletion failed but no timeout and we need requeue it to reconcile it again
|
|
// when timeout.
|
|
func TestDelayReconcile(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
getPods func() []*corev1.Pod
|
|
getWorkloadSpread func() *appsv1alpha1.WorkloadSpread
|
|
getCloneSet func() *appsv1alpha1.CloneSet
|
|
expectWorkloadSpread func() *appsv1alpha1.WorkloadSpread
|
|
expectRequeueAfter time.Duration
|
|
}{
|
|
{
|
|
name: "create three pods, two failed but no timeout",
|
|
getPods: func() []*corev1.Pod {
|
|
return []*corev1.Pod{podDemo.DeepCopy()}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-2": {Time: currentTime.Add(3 * s)},
|
|
"test-pod-3": {Time: currentTime.Add(1 * s)},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses[0].Name = "subset-a"
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectRequeueAfter: 27 * time.Second,
|
|
},
|
|
{
|
|
name: "create five pods, remove two, all failed but no timeout",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 5)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{
|
|
"test-pod-3": {Time: currentTime.Add(3 * s)},
|
|
"test-pod-4": {Time: currentTime.Add(1 * s)},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses[0].Name = "subset-a"
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectRequeueAfter: 12 * time.Second,
|
|
},
|
|
{
|
|
name: "create five pods, create one no timeout, remove one no timeout",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 4)
|
|
for i := range pods {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Status.SubsetStatuses[0].CreatingPods = map[string]metav1.Time{
|
|
"test-pod-4": {Time: currentTime.Add(2 * s)},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses[0].DeletingPods = map[string]metav1.Time{
|
|
"test-pod-1": {Time: currentTime.Add(3 * s)},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses[0].Name = "subset-a"
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
return cloneSetDemo.DeepCopy()
|
|
},
|
|
expectRequeueAfter: 12 * time.Second,
|
|
},
|
|
}
|
|
for _, cs := range cases {
|
|
t.Run(cs.name, func(t *testing.T) {
|
|
currentTime = time.Now()
|
|
workloadSpread := cs.getWorkloadSpread()
|
|
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cs.getCloneSet(), workloadSpread).
|
|
WithIndex(&corev1.Pod{}, fieldindex.IndexNameForOwnerRefUID, func(obj client.Object) []string {
|
|
var owners []string
|
|
for _, ref := range obj.GetOwnerReferences() {
|
|
owners = append(owners, string(ref.UID))
|
|
}
|
|
return owners
|
|
}).Build()
|
|
for _, pod := range cs.getPods() {
|
|
podIn := pod.DeepCopy()
|
|
err := fakeClient.Create(context.TODO(), podIn)
|
|
if err != nil {
|
|
t.Fatalf("create pod failed: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
reconciler := ReconcileWorkloadSpread{
|
|
Client: fakeClient,
|
|
recorder: record.NewFakeRecorder(10),
|
|
controllerFinder: &controllerfinder.ControllerFinder{Client: fakeClient},
|
|
}
|
|
|
|
durationStore = requeueduration.DurationStore{}
|
|
|
|
nsn := types.NamespacedName{Namespace: workloadSpread.Namespace, Name: workloadSpread.Name}
|
|
start := time.Now()
|
|
result, _ := reconciler.Reconcile(context.TODO(), reconcile.Request{NamespacedName: nsn})
|
|
cost := time.Now().Sub(start)
|
|
t.Logf("time cost %f seconds", cost.Seconds())
|
|
if cs.expectRequeueAfter-result.RequeueAfter > 1*time.Second+cost {
|
|
t.Fatalf("requeue key failed")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestManagerExistingPods(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
getPods func() []*corev1.Pod
|
|
getNodes func() []*corev1.Node
|
|
getCloneSet func() *appsv1alpha1.CloneSet
|
|
getWorkloadSpread func() *appsv1alpha1.WorkloadSpread
|
|
getExpectedResult func() (map[string]int32, int, int, int)
|
|
}{
|
|
{
|
|
name: "manage the pods that were created before workloadSpread",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 10)
|
|
// pods that were created before the workloadSpread
|
|
// subset-a
|
|
for i := 0; i < 2; i++ {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Spec.NodeName = "node-a"
|
|
pods[i].Spec.Tolerations = []corev1.Toleration{{
|
|
Key: "schedule-system",
|
|
Value: "unified",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
}}
|
|
pods[i].Spec.Affinity = &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
|
|
NodeSelectorTerms: []corev1.NodeSelectorTerm{{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{{
|
|
Key: "type",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"worker"},
|
|
}},
|
|
}},
|
|
}}}
|
|
}
|
|
// pods that were created before the workloadSpread and injected old workloadSpread
|
|
// subset-a
|
|
for i := 2; i < 4; i++ {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Spec.NodeName = "node-a"
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"unmatched-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
pods[i].Spec.Tolerations = []corev1.Toleration{{
|
|
Key: "schedule-system",
|
|
Value: "unified",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
}}
|
|
pods[i].Spec.Affinity = &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
|
|
NodeSelectorTerms: []corev1.NodeSelectorTerm{{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{{
|
|
Key: "type",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"worker"},
|
|
}},
|
|
}},
|
|
}}}
|
|
}
|
|
// pods that were created after the workloadSpread
|
|
// subset-a
|
|
for i := 4; i < 6; i++ {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-a"}`,
|
|
}
|
|
}
|
|
// pods that were created after the workloadSpread
|
|
// subset-b
|
|
for i := 6; i < 8; i++ {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Annotations = map[string]string{
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-b"}`,
|
|
}
|
|
}
|
|
// pods that cannot match any subset, without other ws annotations
|
|
for i := 8; i < 9; i++ {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Spec.NodeName = "node-c"
|
|
pods[i].Spec.Tolerations = []corev1.Toleration{{
|
|
Key: "node-taint",
|
|
Value: "node-c",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
}}
|
|
}
|
|
// pods that cannot match any subset, with other ws annotations
|
|
for i := 9; i < 10; i++ {
|
|
pods[i] = podDemo.DeepCopy()
|
|
pods[i].Name = fmt.Sprintf("test-pod-%d", i)
|
|
pods[i].Spec.NodeName = "node-a"
|
|
pods[i].Spec.Tolerations = []corev1.Toleration{{
|
|
Key: "node-taint",
|
|
Value: "node-c",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
}}
|
|
pods[i].Annotations = map[string]string{
|
|
PodDeletionCostAnnotation: "200",
|
|
wsutil.MatchedWorkloadSpreadSubsetAnnotations: `{"Name":"test-workloadSpread","Subset":"subset-c"}`,
|
|
}
|
|
}
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets = []appsv1alpha1.WorkloadSpreadSubset{
|
|
{
|
|
Name: "subset-a",
|
|
RequiredNodeSelectorTerm: &corev1.NodeSelectorTerm{MatchExpressions: []corev1.NodeSelectorRequirement{{
|
|
Key: "name-is",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"node-a"},
|
|
}}},
|
|
Tolerations: []corev1.Toleration{{
|
|
Key: "node-taint",
|
|
Value: "node-a",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
}},
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-a"}}}`),
|
|
},
|
|
MaxReplicas: &intstr.IntOrString{Type: intstr.Int, IntVal: 5},
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
RequiredNodeSelectorTerm: &corev1.NodeSelectorTerm{MatchExpressions: []corev1.NodeSelectorRequirement{{
|
|
Key: "name-is",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"node-b"},
|
|
}}},
|
|
},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses = append(workloadSpread.Status.SubsetStatuses, appsv1alpha1.WorkloadSpreadSubsetStatus{
|
|
Name: "subset-b",
|
|
MissingReplicas: int32(-1),
|
|
CreatingPods: map[string]metav1.Time{},
|
|
DeletingPods: map[string]metav1.Time{},
|
|
})
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
cloneSet := cloneSetDemo.DeepCopy()
|
|
cloneSet.Spec.Replicas = ptr.To(int32(10))
|
|
return cloneSet
|
|
},
|
|
getNodes: func() []*corev1.Node {
|
|
return []*corev1.Node{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-a",
|
|
Labels: map[string]string{"name-is": "node-a", "type": "worker"},
|
|
},
|
|
Spec: corev1.NodeSpec{
|
|
Taints: []corev1.Taint{
|
|
{
|
|
Key: "node-taint",
|
|
Value: "node-a",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
TimeAdded: &metav1.Time{Time: time.Now()},
|
|
},
|
|
{
|
|
Key: "schedule-system",
|
|
Value: "unified",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
TimeAdded: &metav1.Time{Time: time.Now()},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node-c",
|
|
Labels: map[string]string{"name-is": "node-c"},
|
|
},
|
|
Spec: corev1.NodeSpec{
|
|
Taints: []corev1.Taint{{
|
|
Key: "node-taint",
|
|
Value: "node-c",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
TimeAdded: &metav1.Time{Time: time.Now()},
|
|
}},
|
|
},
|
|
},
|
|
}
|
|
},
|
|
getExpectedResult: func() (expectedSubsetReplicas map[string]int32, expectedFreePodsCount,
|
|
expectedWithinMaxReplicasCount, expectedBeyondMaxReplicasCount int) {
|
|
expectedSubsetReplicas = map[string]int32{
|
|
"subset-a": 6,
|
|
"subset-b": 2,
|
|
}
|
|
expectedFreePodsCount = 2
|
|
expectedWithinMaxReplicasCount = 7
|
|
expectedBeyondMaxReplicasCount = 1
|
|
return
|
|
},
|
|
},
|
|
{
|
|
name: "manage the pods that were created before workloadSpread, use preferredNodeSelectorTerms to match subset",
|
|
getPods: func() []*corev1.Pod {
|
|
pods := make([]*corev1.Pod, 3)
|
|
pods[0] = podDemo.DeepCopy()
|
|
pods[0].Name = fmt.Sprintf("test-pod-%d", 0)
|
|
pods[0].Spec.NodeName = "worker-1"
|
|
|
|
pods[1] = podDemo.DeepCopy()
|
|
pods[1].Name = fmt.Sprintf("test-pod-%d", 1)
|
|
pods[1].Spec.NodeName = "worker-2"
|
|
|
|
pods[2] = podDemo.DeepCopy()
|
|
pods[2].Name = fmt.Sprintf("test-pod-%d", 2)
|
|
pods[2].Spec.NodeName = "worker-3"
|
|
return pods
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets = []appsv1alpha1.WorkloadSpreadSubset{
|
|
{ // prefer nothing
|
|
Name: "subset-a",
|
|
Tolerations: []corev1.Toleration{{
|
|
Key: "node-taint",
|
|
Operator: corev1.TolerationOpExists,
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
}},
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-a"}}}`),
|
|
},
|
|
MaxReplicas: &intstr.IntOrString{Type: intstr.Int, IntVal: 3},
|
|
},
|
|
{ // prefer worker-2
|
|
Name: "subset-b",
|
|
Tolerations: []corev1.Toleration{{
|
|
Key: "node-taint",
|
|
Operator: corev1.TolerationOpExists,
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
}},
|
|
PreferredNodeSelectorTerms: []corev1.PreferredSchedulingTerm{
|
|
{
|
|
Weight: 200,
|
|
Preference: corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{{
|
|
Key: "type",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"worker-2"},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
Weight: 100,
|
|
Preference: corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{{
|
|
Key: "type",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"worker-3"},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
MaxReplicas: &intstr.IntOrString{Type: intstr.Int, IntVal: 3},
|
|
},
|
|
{ // prefer worker-3
|
|
Name: "subset-c",
|
|
Tolerations: []corev1.Toleration{{
|
|
Key: "node-taint",
|
|
Operator: corev1.TolerationOpExists,
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
}},
|
|
PreferredNodeSelectorTerms: []corev1.PreferredSchedulingTerm{
|
|
{
|
|
Weight: 100,
|
|
Preference: corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{{
|
|
Key: "type",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"worker-2"},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
Weight: 200,
|
|
Preference: corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{{
|
|
Key: "type",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"worker-3"},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
workloadSpread.Status.SubsetStatuses = []appsv1alpha1.WorkloadSpreadSubsetStatus{
|
|
{
|
|
Name: "subset-a",
|
|
MissingReplicas: int32(3),
|
|
CreatingPods: map[string]metav1.Time{},
|
|
DeletingPods: map[string]metav1.Time{},
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
MissingReplicas: int32(3),
|
|
CreatingPods: map[string]metav1.Time{},
|
|
DeletingPods: map[string]metav1.Time{},
|
|
},
|
|
{
|
|
Name: "subset-c",
|
|
MissingReplicas: int32(-1),
|
|
CreatingPods: map[string]metav1.Time{},
|
|
DeletingPods: map[string]metav1.Time{},
|
|
},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
getCloneSet: func() *appsv1alpha1.CloneSet {
|
|
cloneSet := cloneSetDemo.DeepCopy()
|
|
cloneSet.Spec.Replicas = ptr.To(int32(3))
|
|
return cloneSet
|
|
},
|
|
getNodes: func() []*corev1.Node {
|
|
return []*corev1.Node{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "worker-1",
|
|
Labels: map[string]string{"type": "worker-1"},
|
|
},
|
|
Spec: corev1.NodeSpec{
|
|
Taints: []corev1.Taint{
|
|
{
|
|
Key: "node-taint",
|
|
Value: "whatever",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
TimeAdded: &metav1.Time{Time: time.Now()},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "worker-2",
|
|
Labels: map[string]string{"type": "worker-2"},
|
|
},
|
|
Spec: corev1.NodeSpec{
|
|
Taints: []corev1.Taint{
|
|
{
|
|
Key: "node-taint",
|
|
Value: "whatever",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
TimeAdded: &metav1.Time{Time: time.Now()},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "worker-3",
|
|
Labels: map[string]string{"type": "worker-3"},
|
|
},
|
|
Spec: corev1.NodeSpec{
|
|
Taints: []corev1.Taint{
|
|
{
|
|
Key: "node-taint",
|
|
Value: "whatever",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
TimeAdded: &metav1.Time{Time: time.Now()},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
},
|
|
getExpectedResult: func() (expectedSubsetReplicas map[string]int32, expectedFreePodsCount,
|
|
expectedWithinMaxReplicasCount, expectedBeyondMaxReplicasCount int) {
|
|
expectedSubsetReplicas = map[string]int32{
|
|
"subset-a": 1,
|
|
"subset-b": 1,
|
|
"subset-c": 1,
|
|
}
|
|
expectedFreePodsCount = 0
|
|
expectedWithinMaxReplicasCount = 3
|
|
expectedBeyondMaxReplicasCount = 0
|
|
return
|
|
},
|
|
},
|
|
}
|
|
for _, cs := range cases {
|
|
t.Run(cs.name, func(t *testing.T) {
|
|
currentTime = time.Now()
|
|
workloadSpread := cs.getWorkloadSpread()
|
|
builder := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cs.getCloneSet(), workloadSpread)
|
|
builder.WithIndex(&corev1.Pod{}, fieldindex.IndexNameForOwnerRefUID, func(obj client.Object) []string {
|
|
var owners []string
|
|
for _, ref := range obj.GetOwnerReferences() {
|
|
owners = append(owners, string(ref.UID))
|
|
}
|
|
return owners
|
|
})
|
|
for _, pod := range cs.getPods() {
|
|
podIn := pod.DeepCopy()
|
|
builder.WithObjects(podIn)
|
|
}
|
|
for _, node := range cs.getNodes() {
|
|
nodeIn := node.DeepCopy()
|
|
builder.WithObjects(nodeIn)
|
|
}
|
|
builder.WithStatusSubresource(&appsv1alpha1.WorkloadSpread{})
|
|
fakeClient := builder.Build()
|
|
|
|
reconciler := ReconcileWorkloadSpread{
|
|
Client: fakeClient,
|
|
recorder: record.NewFakeRecorder(10),
|
|
controllerFinder: &controllerfinder.ControllerFinder{Client: fakeClient},
|
|
}
|
|
|
|
durationStore = requeueduration.DurationStore{}
|
|
|
|
nsn := types.NamespacedName{Namespace: workloadSpread.Namespace, Name: workloadSpread.Name}
|
|
_, err := reconciler.Reconcile(context.TODO(), reconcile.Request{NamespacedName: nsn})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error when reconciling, err: %s", err.Error())
|
|
}
|
|
workloadSpread, err = getLatestWorkloadSpread(fakeClient, workloadSpread)
|
|
if err != nil {
|
|
t.Fatalf("get workloadspread failed, err: %s", err.Error())
|
|
}
|
|
|
|
expectedSubsetReplicas, expectedFreePodsCount, expectedWithinMaxReplicasCount, expectedBeyondMaxReplicasCount := cs.getExpectedResult()
|
|
|
|
for i := range workloadSpread.Status.SubsetStatuses {
|
|
subset := &workloadSpread.Status.SubsetStatuses[i]
|
|
if subset.Replicas != expectedSubsetReplicas[workloadSpread.Status.SubsetStatuses[i].Name] {
|
|
t.Fatalf("expected %d pods in %s, but got %d\n",
|
|
expectedSubsetReplicas[workloadSpread.Status.SubsetStatuses[i].Name], subset.Name, subset.Replicas)
|
|
}
|
|
}
|
|
|
|
pods, err := getLatestPods(fakeClient, workloadSpread)
|
|
if err != nil {
|
|
t.Fatalf("get workloadspread failed, err: %s", err.Error())
|
|
}
|
|
freePodsCount := 0
|
|
beyondMaxReplicasCount := 0
|
|
withinMaxReplicasCount := 0
|
|
for _, pod := range pods {
|
|
cost, _ := strconv.Atoi(pod.Annotations[PodDeletionCostAnnotation])
|
|
if cost > 0 {
|
|
withinMaxReplicasCount++
|
|
} else if cost >= -100*len(workloadSpread.Spec.Subsets) {
|
|
beyondMaxReplicasCount++
|
|
} else {
|
|
freePodsCount++
|
|
}
|
|
}
|
|
if freePodsCount != expectedFreePodsCount {
|
|
t.Fatalf("unexpected %d pods marked as minimum deletion cost, but got %d", expectedFreePodsCount, freePodsCount)
|
|
}
|
|
if withinMaxReplicasCount != expectedWithinMaxReplicasCount {
|
|
t.Fatalf("unexpected %d pods marked positive deletion cost, but got %d", expectedWithinMaxReplicasCount, withinMaxReplicasCount)
|
|
}
|
|
if beyondMaxReplicasCount != expectedBeyondMaxReplicasCount {
|
|
t.Fatalf("unexpected %d pods marked negative deletion cost, but got %d", expectedBeyondMaxReplicasCount, beyondMaxReplicasCount)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func getLatestWorkloadSpread(client client.Client, workloadSpread *appsv1alpha1.WorkloadSpread) (*appsv1alpha1.WorkloadSpread, error) {
|
|
newWorkloadSpread := &appsv1alpha1.WorkloadSpread{}
|
|
key := types.NamespacedName{
|
|
Namespace: workloadSpread.Namespace,
|
|
Name: workloadSpread.Name,
|
|
}
|
|
err := client.Get(context.TODO(), key, newWorkloadSpread)
|
|
return newWorkloadSpread, err
|
|
}
|
|
|
|
func getLatestPods(c client.Client, workloadSpread *appsv1alpha1.WorkloadSpread) ([]*corev1.Pod, error) {
|
|
selector := &metav1.LabelSelector{
|
|
MatchLabels: map[string]string{
|
|
"app": "nginx",
|
|
}}
|
|
labelSelector, err := metav1.LabelSelectorAsSelector(selector)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
podList := &corev1.PodList{}
|
|
opts := &client.ListOptions{
|
|
Namespace: workloadSpread.Namespace,
|
|
LabelSelector: labelSelector,
|
|
}
|
|
err = c.List(context.TODO(), podList, opts)
|
|
matchedPods := make([]*corev1.Pod, len(podList.Items))
|
|
for i := range podList.Items {
|
|
matchedPods[i] = &podList.Items[i]
|
|
}
|
|
return matchedPods, err
|
|
}
|