mirror of https://github.com/openkruise/kruise.git
944 lines
29 KiB
Go
944 lines
29 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,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
package validating
|
|
|
|
import (
|
|
"strconv"
|
|
"testing"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
"k8s.io/utils/pointer"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
|
|
appsv1alpha1 "github.com/openkruise/kruise/apis/apps/v1alpha1"
|
|
)
|
|
|
|
var (
|
|
scheme *runtime.Scheme
|
|
kruiseKindCloneSet = appsv1alpha1.SchemeGroupVersion.WithKind("CloneSet")
|
|
handler = &WorkloadSpreadCreateUpdateHandler{}
|
|
maxReplicasDemo = intstr.FromInt(50)
|
|
patchResource = []byte(`{"spec":{"containers":[{"name":"main","resources":{"limits":{"cpu":"2","memory":"8000Mi"}}}]}}`)
|
|
workloadSpreadDemo = &appsv1alpha1.WorkloadSpread{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "workloadSpread", Namespace: metav1.NamespaceDefault},
|
|
Spec: appsv1alpha1.WorkloadSpreadSpec{
|
|
TargetReference: &appsv1alpha1.TargetReference{
|
|
APIVersion: kruiseKindCloneSet.GroupVersion().String(),
|
|
Kind: kruiseKindCloneSet.Kind,
|
|
Name: "demo",
|
|
},
|
|
Subsets: []appsv1alpha1.WorkloadSpreadSubset{
|
|
{
|
|
Name: "subset-a",
|
|
MaxReplicas: &maxReplicasDemo,
|
|
RequiredNodeSelectorTerm: &corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "topology.kubernetes.io/zone",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"zone-a"},
|
|
},
|
|
},
|
|
},
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "ecs",
|
|
Operator: corev1.TolerationOpExists,
|
|
},
|
|
},
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-a"}}}`),
|
|
},
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
MaxReplicas: &maxReplicasDemo,
|
|
RequiredNodeSelectorTerm: &corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "topology.kubernetes.io/zone",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"zone-b"},
|
|
},
|
|
},
|
|
},
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "ecs",
|
|
Operator: corev1.TolerationOpExists,
|
|
},
|
|
},
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-b"}}}`),
|
|
},
|
|
},
|
|
{
|
|
Name: "subset-c",
|
|
RequiredNodeSelectorTerm: &corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "topology.kubernetes.io/zone",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"zone-c"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
scheme = runtime.NewScheme()
|
|
_ = appsv1alpha1.AddToScheme(scheme)
|
|
}
|
|
|
|
func TestValidateWorkloadSpreadCreate(t *testing.T) {
|
|
targetRef := appsv1alpha1.TargetReference{
|
|
APIVersion: kruiseKindCloneSet.GroupVersion().String(),
|
|
Kind: kruiseKindCloneSet.Kind,
|
|
Name: "test",
|
|
}
|
|
replicas1 := intstr.FromInt(50)
|
|
replicas2 := intstr.FromString("50%")
|
|
successCases := []appsv1alpha1.WorkloadSpread{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "ws-1", Namespace: metav1.NamespaceDefault},
|
|
Spec: appsv1alpha1.WorkloadSpreadSpec{
|
|
TargetReference: &targetRef,
|
|
Subsets: []appsv1alpha1.WorkloadSpreadSubset{
|
|
{
|
|
Name: "subset-a",
|
|
MaxReplicas: &replicas1,
|
|
RequiredNodeSelectorTerm: &corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "topology.kubernetes.io/zone",
|
|
Operator: corev1.NodeSelectorOpNotIn,
|
|
Values: []string{"zone-a"},
|
|
},
|
|
},
|
|
},
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "ecs",
|
|
Operator: corev1.TolerationOpExists,
|
|
},
|
|
},
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-a"}}}`),
|
|
},
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
MaxReplicas: nil,
|
|
RequiredNodeSelectorTerm: &corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "topology.kubernetes.io/zone",
|
|
Operator: corev1.NodeSelectorOpNotIn,
|
|
Values: []string{"zone-a"},
|
|
},
|
|
},
|
|
},
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "ecs",
|
|
Operator: corev1.TolerationOpExists,
|
|
},
|
|
},
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-b"}}}`),
|
|
},
|
|
},
|
|
},
|
|
ScheduleStrategy: appsv1alpha1.WorkloadSpreadScheduleStrategy{
|
|
Type: appsv1alpha1.FixedWorkloadSpreadScheduleStrategyType,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "ws-1", Namespace: metav1.NamespaceDefault},
|
|
Spec: appsv1alpha1.WorkloadSpreadSpec{
|
|
TargetReference: &appsv1alpha1.TargetReference{
|
|
APIVersion: "apps/v1",
|
|
Kind: "StatefulSet",
|
|
Name: "demo",
|
|
},
|
|
Subsets: []appsv1alpha1.WorkloadSpreadSubset{
|
|
{
|
|
Name: "subset-a",
|
|
MaxReplicas: &replicas1,
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-a"}}}`),
|
|
},
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
MaxReplicas: nil,
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-b"}}}`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "ws-1", Namespace: metav1.NamespaceDefault},
|
|
Spec: appsv1alpha1.WorkloadSpreadSpec{
|
|
TargetReference: &appsv1alpha1.TargetReference{
|
|
APIVersion: "apps.kruise.io/v1alpha1",
|
|
Kind: "StatefulSet",
|
|
Name: "demo",
|
|
},
|
|
Subsets: []appsv1alpha1.WorkloadSpreadSubset{
|
|
{
|
|
Name: "subset-a",
|
|
MaxReplicas: &replicas1,
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-a"}}}`),
|
|
},
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
MaxReplicas: nil,
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-b"}}}`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "ws-1", Namespace: metav1.NamespaceDefault},
|
|
Spec: appsv1alpha1.WorkloadSpreadSpec{
|
|
TargetReference: &appsv1alpha1.TargetReference{
|
|
APIVersion: "apps.kruise.io/v1beta1",
|
|
Kind: "StatefulSet",
|
|
Name: "demo",
|
|
},
|
|
Subsets: []appsv1alpha1.WorkloadSpreadSubset{
|
|
{
|
|
Name: "subset-a",
|
|
MaxReplicas: &replicas1,
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-a"}}}`),
|
|
},
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
MaxReplicas: nil,
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-b"}}}`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "ws-2", Namespace: metav1.NamespaceDefault},
|
|
Spec: appsv1alpha1.WorkloadSpreadSpec{
|
|
TargetReference: &appsv1alpha1.TargetReference{
|
|
APIVersion: controllerKindDep.GroupVersion().String(),
|
|
Kind: controllerKindDep.Kind,
|
|
Name: "test",
|
|
},
|
|
Subsets: []appsv1alpha1.WorkloadSpreadSubset{
|
|
{
|
|
Name: "subset-a",
|
|
MaxReplicas: &replicas1,
|
|
PreferredNodeSelectorTerms: []corev1.PreferredSchedulingTerm{
|
|
{
|
|
Weight: 20,
|
|
Preference: corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "topology.kubernetes.io/zone",
|
|
Operator: corev1.NodeSelectorOpNotIn,
|
|
Values: []string{"zone-a"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "ecs",
|
|
Operator: corev1.TolerationOpExists,
|
|
},
|
|
},
|
|
Patch: runtime.RawExtension{
|
|
Raw: patchResource,
|
|
},
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
MaxReplicas: nil,
|
|
RequiredNodeSelectorTerm: &corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "topology.kubernetes.io/zone",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"zone-b"},
|
|
},
|
|
},
|
|
},
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "ecs",
|
|
Operator: corev1.TolerationOpExists,
|
|
},
|
|
},
|
|
Patch: runtime.RawExtension{
|
|
Raw: patchResource,
|
|
},
|
|
},
|
|
},
|
|
ScheduleStrategy: appsv1alpha1.WorkloadSpreadScheduleStrategy{
|
|
Type: appsv1alpha1.AdaptiveWorkloadSpreadScheduleStrategyType,
|
|
Adaptive: &appsv1alpha1.AdaptiveWorkloadSpreadStrategy{
|
|
RescheduleCriticalSeconds: pointer.Int32Ptr(30),
|
|
DisableSimulationSchedule: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "ws-3", Namespace: metav1.NamespaceDefault},
|
|
Spec: appsv1alpha1.WorkloadSpreadSpec{
|
|
TargetReference: &appsv1alpha1.TargetReference{
|
|
APIVersion: controllerKindJob.GroupVersion().String(),
|
|
Kind: controllerKindJob.Kind,
|
|
Name: "test",
|
|
},
|
|
Subsets: []appsv1alpha1.WorkloadSpreadSubset{
|
|
{
|
|
Name: "subset-a",
|
|
MaxReplicas: &replicas2,
|
|
RequiredNodeSelectorTerm: &corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "topology.kubernetes.io/zone",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"zone-a"},
|
|
},
|
|
},
|
|
},
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "ecs",
|
|
Operator: corev1.TolerationOpExists,
|
|
},
|
|
},
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-a"}}}`),
|
|
},
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
MaxReplicas: &replicas2,
|
|
RequiredNodeSelectorTerm: &corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "topology.kubernetes.io/zone",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"zone-b"},
|
|
},
|
|
},
|
|
},
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "ecs",
|
|
Operator: corev1.TolerationOpExists,
|
|
},
|
|
},
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-b"}}}`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "ws-4", Namespace: metav1.NamespaceDefault},
|
|
Spec: appsv1alpha1.WorkloadSpreadSpec{
|
|
TargetReference: &appsv1alpha1.TargetReference{
|
|
APIVersion: controllerKindJob.GroupVersion().String(),
|
|
Kind: controllerKindJob.Kind,
|
|
Name: "test",
|
|
},
|
|
Subsets: []appsv1alpha1.WorkloadSpreadSubset{
|
|
{
|
|
Name: "subset-a",
|
|
MaxReplicas: &replicas2,
|
|
RequiredNodeSelectorTerm: &corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "topology.kubernetes.io/zone",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"zone-a"},
|
|
},
|
|
},
|
|
},
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "ecs",
|
|
Operator: corev1.TolerationOpExists,
|
|
},
|
|
},
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-a"}}}`),
|
|
},
|
|
},
|
|
{
|
|
Name: "subset-b",
|
|
MaxReplicas: &replicas2,
|
|
RequiredNodeSelectorTerm: &corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "topology.kubernetes.io/zone",
|
|
Operator: corev1.NodeSelectorOpIn,
|
|
Values: []string{"zone-b"},
|
|
},
|
|
},
|
|
},
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "ecs",
|
|
Operator: corev1.TolerationOpExists,
|
|
},
|
|
},
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-b"}}}`),
|
|
},
|
|
},
|
|
{
|
|
Name: "subset-c",
|
|
MaxReplicas: nil,
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "ecs",
|
|
Operator: corev1.TolerationOpExists,
|
|
},
|
|
},
|
|
Patch: runtime.RawExtension{
|
|
Raw: []byte(`{"metadata":{"annotations":{"subset":"subset-c"}}}`),
|
|
},
|
|
},
|
|
},
|
|
ScheduleStrategy: appsv1alpha1.WorkloadSpreadScheduleStrategy{
|
|
Type: appsv1alpha1.AdaptiveWorkloadSpreadScheduleStrategyType,
|
|
Adaptive: &appsv1alpha1.AdaptiveWorkloadSpreadStrategy{
|
|
RescheduleCriticalSeconds: pointer.Int32Ptr(30),
|
|
DisableSimulationSchedule: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for i, successCase := range successCases {
|
|
t.Run("success case "+strconv.Itoa(i), func(t *testing.T) {
|
|
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(workloadSpreadDemo).Build()
|
|
handler.Client = fakeClient
|
|
if errs := handler.validatingWorkloadSpreadFn(&successCase); len(errs) != 0 {
|
|
t.Errorf("expected success: %v", errs)
|
|
}
|
|
})
|
|
}
|
|
|
|
errorCases := []struct {
|
|
name string
|
|
getWorkloadSpread func() *appsv1alpha1.WorkloadSpread
|
|
errorSuffix string
|
|
}{
|
|
{
|
|
name: "targetRef is nil",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.TargetReference = nil
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.targetRef",
|
|
},
|
|
{
|
|
name: "targetRef's APIVersion is nil",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.TargetReference.APIVersion = ""
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.targetRef",
|
|
},
|
|
{
|
|
name: "targetRef's Kind is nil",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.TargetReference.APIVersion = ""
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.targetRef",
|
|
},
|
|
{
|
|
name: "targetRef's Name is nil",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.TargetReference.APIVersion = ""
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.targetRef",
|
|
},
|
|
{
|
|
name: "subsets is nil",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets = nil
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets",
|
|
},
|
|
{
|
|
name: "statefulset group error",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.TargetReference = &appsv1alpha1.TargetReference{
|
|
APIVersion: "rollouts.kruise.io/v2",
|
|
Kind: "StatefulSet",
|
|
Name: "demo",
|
|
}
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.targetRef",
|
|
},
|
|
{
|
|
name: "statefulset subset is percentage",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.TargetReference = &appsv1alpha1.TargetReference{
|
|
APIVersion: "apps.kruise.io/v1beta1",
|
|
Kind: "StatefulSet",
|
|
Name: "demo",
|
|
}
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &replicas2
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[0].maxReplicas",
|
|
},
|
|
|
|
// {
|
|
// name: "one subset",
|
|
// getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
// workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
// workloadSpread.Spec.Subsets = []appsv1alpha1.WorkloadSpreadSubset{
|
|
// {
|
|
// Name: "subset-a",
|
|
// MaxReplicas: nil,
|
|
// },
|
|
// }
|
|
// return workloadSpread
|
|
// },
|
|
// errorSuffix: "spec.subsets",
|
|
// },
|
|
{
|
|
name: "subset[0]'name is empty",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].Name = ""
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[0].name",
|
|
},
|
|
{
|
|
name: "subset[1]'name is empty",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[1].Name = ""
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[1].name",
|
|
},
|
|
{
|
|
name: "subset[1]'name is conflict with subsets[0]",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].Name = workloadSpread.Spec.Subsets[1].Name
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[1].name",
|
|
},
|
|
// {
|
|
// name: "subset[0]'s requiredNodeSelectorTerm, preferredNodeSelectorTerms and tolerations are all empty",
|
|
// getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
// workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
// workloadSpread.Spec.Subsets[0].RequiredNodeSelectorTerm = nil
|
|
// workloadSpread.Spec.Subsets[0].PreferredNodeSelectorTerms = nil
|
|
// workloadSpread.Spec.Subsets[0].Tolerations = nil
|
|
// return workloadSpread
|
|
// },
|
|
// errorSuffix: "spec.subsets[0].requiredNodeSelectorTerm",
|
|
// },
|
|
{
|
|
name: "requiredNodeSelectorTerm are not valid",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].RequiredNodeSelectorTerm = &corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "key",
|
|
Operator: corev1.NodeSelectorOpExists,
|
|
Values: []string{"unexpected"},
|
|
},
|
|
},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[0].requiredNodeSelectorTerm.matchExpressions[0].values",
|
|
},
|
|
{
|
|
name: "preferredSchedulingTerms' weight are not valid",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].PreferredNodeSelectorTerms = []corev1.PreferredSchedulingTerm{
|
|
{
|
|
Weight: 101,
|
|
Preference: corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "key",
|
|
Operator: corev1.NodeSelectorOpExists,
|
|
Values: []string{"unexpected"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[0].preferredSchedulingTerms[0].weight",
|
|
},
|
|
{
|
|
name: "preferredSchedulingTerms are not valid",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.Subsets[0].PreferredNodeSelectorTerms = []corev1.PreferredSchedulingTerm{
|
|
{
|
|
Weight: 50,
|
|
Preference: corev1.NodeSelectorTerm{
|
|
MatchExpressions: []corev1.NodeSelectorRequirement{
|
|
{
|
|
Key: "key",
|
|
Operator: corev1.NodeSelectorOpExists,
|
|
Values: []string{"unexpected"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[0].preferredSchedulingTerms[0].preference.matchExpressions[0].values",
|
|
},
|
|
{
|
|
name: "subset-a's maxReplicas < 0",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
maxReplicas := intstr.FromInt(-100)
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &maxReplicas
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[0].maxReplicas",
|
|
},
|
|
{
|
|
name: "subset-a's maxReplicas = 0.2%",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
maxReplicas := intstr.FromString("0.2%")
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &maxReplicas
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[0].maxReplicas",
|
|
},
|
|
{
|
|
name: "subset-a's maxReplicas = 50.2%",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
maxReplicas := intstr.FromString("50.2%")
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &maxReplicas
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[0].maxReplicas",
|
|
},
|
|
{
|
|
name: "subset-a's maxReplicas = 101%",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
maxReplicas := intstr.FromString("101%")
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &maxReplicas
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[0].maxReplicas",
|
|
},
|
|
{
|
|
name: "subset-a's maxReplicas = 40%, subset-b's maxReplicas = 70%",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
maxReplicas1 := intstr.FromString("40%")
|
|
maxReplicas2 := intstr.FromString("70%")
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &maxReplicas1
|
|
workloadSpread.Spec.Subsets[1].MaxReplicas = &maxReplicas2
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[1].maxReplicas",
|
|
},
|
|
{
|
|
name: "subset-a's maxReplicas = 40%, subset-b's maxReplicas = 20%,, subset-c's maxReplicas = 20%",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
maxReplicas1 := intstr.FromString("40%")
|
|
maxReplicas2 := intstr.FromString("20%")
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &maxReplicas1
|
|
workloadSpread.Spec.Subsets[1].MaxReplicas = &maxReplicas2
|
|
workloadSpread.Spec.Subsets[2].MaxReplicas = &maxReplicas2
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[0].maxReplicas",
|
|
},
|
|
{
|
|
name: "subset-a's maxReplicas = -1%",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
maxReplicas := intstr.FromString("-1%")
|
|
workloadSpread.Spec.Subsets[0].MaxReplicas = &maxReplicas
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.subsets[0].maxReplicas",
|
|
},
|
|
{
|
|
name: "scheduleStrategy's type is not valid",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.ScheduleStrategy.Type = "random"
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.scheduleStrategy.type",
|
|
},
|
|
{
|
|
name: "rescheduleCriticalSeconds = -1",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.ScheduleStrategy = appsv1alpha1.WorkloadSpreadScheduleStrategy{
|
|
Type: appsv1alpha1.AdaptiveWorkloadSpreadScheduleStrategyType,
|
|
Adaptive: &appsv1alpha1.AdaptiveWorkloadSpreadStrategy{
|
|
RescheduleCriticalSeconds: pointer.Int32Ptr(-1),
|
|
DisableSimulationSchedule: true,
|
|
},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.scheduleStrategy.adaptive.rescheduleCriticalSeconds",
|
|
},
|
|
{
|
|
name: "rescheduleCriticalSeconds < 0",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.ScheduleStrategy = appsv1alpha1.WorkloadSpreadScheduleStrategy{
|
|
Type: appsv1alpha1.AdaptiveWorkloadSpreadScheduleStrategyType,
|
|
Adaptive: &appsv1alpha1.AdaptiveWorkloadSpreadStrategy{
|
|
RescheduleCriticalSeconds: pointer.Int32Ptr(-20),
|
|
DisableSimulationSchedule: true,
|
|
},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.scheduleStrategy.adaptive.rescheduleCriticalSeconds",
|
|
},
|
|
{
|
|
name: "scheduleStrategy's type is not matched",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.ScheduleStrategy = appsv1alpha1.WorkloadSpreadScheduleStrategy{
|
|
Type: appsv1alpha1.FixedWorkloadSpreadScheduleStrategyType,
|
|
Adaptive: &appsv1alpha1.AdaptiveWorkloadSpreadStrategy{
|
|
RescheduleCriticalSeconds: pointer.Int32Ptr(20),
|
|
DisableSimulationSchedule: true,
|
|
},
|
|
}
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.scheduleStrategy.type",
|
|
},
|
|
{
|
|
name: "the last subset's maxReplicas is not nil when using adaptive",
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.ScheduleStrategy = appsv1alpha1.WorkloadSpreadScheduleStrategy{
|
|
Type: appsv1alpha1.AdaptiveWorkloadSpreadScheduleStrategyType,
|
|
Adaptive: &appsv1alpha1.AdaptiveWorkloadSpreadStrategy{
|
|
RescheduleCriticalSeconds: pointer.Int32Ptr(20),
|
|
DisableSimulationSchedule: true,
|
|
},
|
|
}
|
|
workloadSpread.Spec.Subsets[2].MaxReplicas = &maxReplicasDemo
|
|
return workloadSpread
|
|
},
|
|
errorSuffix: "spec.scheduleStrategy.adaptive",
|
|
},
|
|
}
|
|
|
|
for _, errorCase := range errorCases {
|
|
t.Run(errorCase.name, func(t *testing.T) {
|
|
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(workloadSpreadDemo).Build()
|
|
handler.Client = fakeClient
|
|
errs := handler.validatingWorkloadSpreadFn(errorCase.getWorkloadSpread())
|
|
if len(errs) == 0 {
|
|
t.Errorf("expected failure for %s", errorCase.name)
|
|
}
|
|
|
|
var exist bool
|
|
for i := range errs {
|
|
f := errs[i].Field
|
|
if f == errorCase.errorSuffix {
|
|
exist = true
|
|
break
|
|
}
|
|
}
|
|
if !exist {
|
|
t.Errorf("%s: missing prefix for: %v", errorCase.name, errs)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateWorkloadSpreadTargetRefUpdate(t *testing.T) {
|
|
oldWorkloadSpread := workloadSpreadDemo.DeepCopy()
|
|
errorSuffix := "spec.targetRef"
|
|
|
|
workloadSpread1 := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread1.Spec.TargetReference.APIVersion = "apps/v1"
|
|
workloadSpread2 := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread2.Spec.TargetReference.Kind = "Deployment"
|
|
workloadSpread3 := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread3.Spec.TargetReference.Name = "deploy-1"
|
|
|
|
errorCases := []struct {
|
|
name string
|
|
newWorkloadSpread *appsv1alpha1.WorkloadSpread
|
|
}{
|
|
{
|
|
name: "update group",
|
|
newWorkloadSpread: workloadSpread1,
|
|
},
|
|
{
|
|
name: "update kind",
|
|
newWorkloadSpread: workloadSpread2,
|
|
},
|
|
{
|
|
name: "update name",
|
|
newWorkloadSpread: workloadSpread3,
|
|
},
|
|
}
|
|
for _, cs := range errorCases {
|
|
t.Run(cs.name, func(t *testing.T) {
|
|
allErrors := validateWorkloadSpreadTargetRefUpdate(oldWorkloadSpread.Spec.TargetReference, cs.newWorkloadSpread.Spec.TargetReference, field.NewPath("spec"))
|
|
if allErrors[0].Field != errorSuffix {
|
|
t.Errorf("%s: missing prefix for: %v", errorSuffix, allErrors)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateWorkloadSpreadConflict(t *testing.T) {
|
|
workloadSpread1 := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread1.Name = "ws-1"
|
|
workloadSpread1.Spec.TargetReference.Name = "test1"
|
|
|
|
workloadSpread2 := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread2.Name = "ws-2"
|
|
workloadSpread2.Spec.TargetReference.Name = "test2"
|
|
|
|
workloadSpread3 := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread3.Name = "ws-3"
|
|
workloadSpread3.Spec.TargetReference.Name = "test3"
|
|
|
|
successCases := []struct {
|
|
name string
|
|
getOthers func() []appsv1alpha1.WorkloadSpread
|
|
getWorkloadSpread func() *appsv1alpha1.WorkloadSpread
|
|
}{
|
|
{
|
|
name: "group is different",
|
|
getOthers: func() []appsv1alpha1.WorkloadSpread {
|
|
return []appsv1alpha1.WorkloadSpread{*workloadSpread1, *workloadSpread2, *workloadSpread3}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.TargetReference.APIVersion = "apps/v1"
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "kind is different",
|
|
getOthers: func() []appsv1alpha1.WorkloadSpread {
|
|
return []appsv1alpha1.WorkloadSpread{*workloadSpread1, *workloadSpread2, *workloadSpread3}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.TargetReference.Kind = "Deployment"
|
|
return workloadSpread
|
|
},
|
|
},
|
|
{
|
|
name: "name is different",
|
|
getOthers: func() []appsv1alpha1.WorkloadSpread {
|
|
return []appsv1alpha1.WorkloadSpread{*workloadSpread1, *workloadSpread2, *workloadSpread3}
|
|
},
|
|
getWorkloadSpread: func() *appsv1alpha1.WorkloadSpread {
|
|
workloadSpread := workloadSpreadDemo.DeepCopy()
|
|
workloadSpread.Spec.TargetReference.Name = "test"
|
|
return workloadSpread
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, successCase := range successCases {
|
|
t.Run("success case "+strconv.Itoa(i), func(t *testing.T) {
|
|
if errs := validateWorkloadSpreadConflict(successCase.getWorkloadSpread(), successCase.getOthers(), field.NewPath("spec")); len(errs) != 0 {
|
|
t.Errorf("expected success: %v", errs)
|
|
}
|
|
})
|
|
}
|
|
|
|
workloadSpread1.Spec.TargetReference.Name = "test1"
|
|
workloadSpread2.Spec.TargetReference.Name = "test1"
|
|
workloadSpread3.Spec.TargetReference.Name = "test2"
|
|
|
|
others := []appsv1alpha1.WorkloadSpread{*workloadSpread1, *workloadSpread2, *workloadSpread3}
|
|
errorSuffix := "spec.targetRef"
|
|
|
|
for i := 0; i < 2; i++ {
|
|
t.Run("", func(t *testing.T) {
|
|
allErrors := validateWorkloadSpreadConflict(&others[i], others, field.NewPath("spec"))
|
|
if allErrors[0].Field != errorSuffix {
|
|
t.Errorf("%s: missing prefix for: %v", errorSuffix, allErrors)
|
|
}
|
|
})
|
|
}
|
|
}
|