Merge pull request #96745 from lingsamuel/scheduler-validation

Use field.Error(s) in scheduler plugin args validation
This commit is contained in:
Kubernetes Prow Robot 2020-12-09 02:10:50 -08:00 committed by GitHub
commit 5996839425
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 506 additions and 173 deletions

View File

@ -29,39 +29,46 @@ import (
// ValidateDefaultPreemptionArgs validates that DefaultPreemptionArgs are correct.
func ValidateDefaultPreemptionArgs(args config.DefaultPreemptionArgs) error {
if err := validateMinCandidateNodesPercentage(args.MinCandidateNodesPercentage); err != nil {
return err
var path *field.Path
var allErrs field.ErrorList
percentagePath := path.Child("minCandidateNodesPercentage")
absolutePath := path.Child("minCandidateNodesAbsolute")
if err := validateMinCandidateNodesPercentage(args.MinCandidateNodesPercentage, percentagePath); err != nil {
allErrs = append(allErrs, err)
}
if err := validateMinCandidateNodesAbsolute(args.MinCandidateNodesAbsolute); err != nil {
return err
if err := validateMinCandidateNodesAbsolute(args.MinCandidateNodesAbsolute, absolutePath); err != nil {
allErrs = append(allErrs, err)
}
if args.MinCandidateNodesPercentage == 0 && args.MinCandidateNodesAbsolute == 0 {
return fmt.Errorf("both minCandidateNodesPercentage and minCandidateNodesAbsolute cannot be zero")
allErrs = append(allErrs,
field.Invalid(percentagePath, args.MinCandidateNodesPercentage, "cannot be zero at the same time as minCandidateNodesAbsolute"),
field.Invalid(absolutePath, args.MinCandidateNodesAbsolute, "cannot be zero at the same time as minCandidateNodesPercentage"))
}
return nil
return allErrs.ToAggregate()
}
// validateMinCandidateNodesPercentage validates that
// minCandidateNodesPercentage is within the allowed range.
func validateMinCandidateNodesPercentage(minCandidateNodesPercentage int32) error {
func validateMinCandidateNodesPercentage(minCandidateNodesPercentage int32, p *field.Path) *field.Error {
if minCandidateNodesPercentage < 0 || minCandidateNodesPercentage > 100 {
return fmt.Errorf("minCandidateNodesPercentage is not in the range [0, 100]")
return field.Invalid(p, minCandidateNodesPercentage, "not in valid range [0, 100]")
}
return nil
}
// validateMinCandidateNodesAbsolute validates that minCandidateNodesAbsolute
// is within the allowed range.
func validateMinCandidateNodesAbsolute(minCandidateNodesAbsolute int32) error {
func validateMinCandidateNodesAbsolute(minCandidateNodesAbsolute int32, p *field.Path) *field.Error {
if minCandidateNodesAbsolute < 0 {
return fmt.Errorf("minCandidateNodesAbsolute is not in the range [0, inf)")
return field.Invalid(p, minCandidateNodesAbsolute, "not in valid range [0, inf)")
}
return nil
}
// ValidateInterPodAffinityArgs validates that InterPodAffinityArgs are correct.
func ValidateInterPodAffinityArgs(args config.InterPodAffinityArgs) error {
return ValidateHardPodAffinityWeight(field.NewPath("hardPodAffinityWeight"), args.HardPodAffinityWeight)
var path *field.Path
return ValidateHardPodAffinityWeight(path.Child("hardPodAffinityWeight"), args.HardPodAffinityWeight)
}
// ValidateHardPodAffinityWeight validates that weight is within allowed range.
@ -72,7 +79,7 @@ func ValidateHardPodAffinityWeight(path *field.Path, w int32) error {
)
if w < minHardPodAffinityWeight || w > maxHardPodAffinityWeight {
msg := fmt.Sprintf("not in valid range [%d-%d]", minHardPodAffinityWeight, maxHardPodAffinityWeight)
msg := fmt.Sprintf("not in valid range [%d, %d]", minHardPodAffinityWeight, maxHardPodAffinityWeight)
return field.Invalid(path, w, msg)
}
return nil
@ -80,44 +87,50 @@ func ValidateHardPodAffinityWeight(path *field.Path, w int32) error {
// ValidateNodeLabelArgs validates that NodeLabelArgs are correct.
func ValidateNodeLabelArgs(args config.NodeLabelArgs) error {
if err := validateNoConflict(args.PresentLabels, args.AbsentLabels); err != nil {
return err
}
if err := validateNoConflict(args.PresentLabelsPreference, args.AbsentLabelsPreference); err != nil {
return err
}
return nil
var path *field.Path
var allErrs field.ErrorList
allErrs = append(allErrs, validateNoConflict(args.PresentLabels, args.AbsentLabels,
path.Child("presentLabels"), path.Child("absentLabels"))...)
allErrs = append(allErrs, validateNoConflict(args.PresentLabelsPreference, args.AbsentLabelsPreference,
path.Child("presentLabelsPreference"), path.Child("absentLabelsPreference"))...)
return allErrs.ToAggregate()
}
// validateNoConflict validates that presentLabels and absentLabels do not conflict.
func validateNoConflict(presentLabels []string, absentLabels []string) error {
m := make(map[string]struct{}, len(presentLabels))
for _, l := range presentLabels {
m[l] = struct{}{}
func validateNoConflict(presentLabels, absentLabels []string, presentPath, absentPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
m := make(map[string]int, len(presentLabels)) // label -> index
for i, l := range presentLabels {
m[l] = i
}
for _, l := range absentLabels {
if _, ok := m[l]; ok {
return fmt.Errorf("detecting at least one label (e.g., %q) that exist in both the present(%+v) and absent(%+v) label list", l, presentLabels, absentLabels)
for i, l := range absentLabels {
if j, ok := m[l]; ok {
allErrs = append(allErrs, field.Invalid(presentPath.Index(j), l,
fmt.Sprintf("conflict with %v", absentPath.Index(i).String())))
}
}
return nil
return allErrs
}
// ValidatePodTopologySpreadArgs validates that PodTopologySpreadArgs are correct.
// It replicates the validation from pkg/apis/core/validation.validateTopologySpreadConstraints
// with an additional check for .labelSelector to be nil.
func ValidatePodTopologySpreadArgs(args *config.PodTopologySpreadArgs) error {
var path *field.Path
var allErrs field.ErrorList
if err := validateDefaultingType(field.NewPath("defaultingType"), args.DefaultingType, args.DefaultConstraints); err != nil {
if err := validateDefaultingType(path.Child("defaultingType"), args.DefaultingType, args.DefaultConstraints); err != nil {
allErrs = append(allErrs, err)
}
path := field.NewPath("defaultConstraints")
defaultConstraintsPath := path.Child("defaultConstraints")
for i, c := range args.DefaultConstraints {
p := path.Index(i)
p := defaultConstraintsPath.Index(i)
if c.MaxSkew <= 0 {
f := p.Child("maxSkew")
allErrs = append(allErrs, field.Invalid(f, c.MaxSkew, "must be greater than zero"))
allErrs = append(allErrs, field.Invalid(f, c.MaxSkew, "not in valid range (0, inf)"))
}
allErrs = append(allErrs, validateTopologyKey(p.Child("topologyKey"), c.TopologyKey)...)
if err := validateWhenUnsatisfiable(p.Child("whenUnsatisfiable"), c.WhenUnsatisfiable); err != nil {
@ -127,7 +140,7 @@ func ValidatePodTopologySpreadArgs(args *config.PodTopologySpreadArgs) error {
f := field.Forbidden(p.Child("labelSelector"), "constraint must not define a selector, as they deduced for each pod")
allErrs = append(allErrs, f)
}
if err := validateConstraintNotRepeat(path, args.DefaultConstraints, i); err != nil {
if err := validateConstraintNotRepeat(defaultConstraintsPath, args.DefaultConstraints, i); err != nil {
allErrs = append(allErrs, err)
}
}
@ -139,7 +152,7 @@ func ValidatePodTopologySpreadArgs(args *config.PodTopologySpreadArgs) error {
func validateDefaultingType(p *field.Path, v config.PodTopologySpreadConstraintsDefaulting, constraints []v1.TopologySpreadConstraint) *field.Error {
if v != config.SystemDefaulting && v != config.ListDefaulting {
return field.Invalid(p, v, fmt.Sprintf("must be one of {%q, %q}", config.SystemDefaulting, config.ListDefaulting))
return field.NotSupported(p, v, []string{string(config.SystemDefaulting), string(config.ListDefaulting)})
}
if v == config.SystemDefaulting && len(constraints) > 0 {
return field.Invalid(p, v, "when .defaultConstraints are not empty")
@ -182,16 +195,14 @@ func validateConstraintNotRepeat(path *field.Path, constraints []v1.TopologySpre
// ValidateRequestedToCapacityRatioArgs validates that RequestedToCapacityRatioArgs are correct.
func ValidateRequestedToCapacityRatioArgs(args config.RequestedToCapacityRatioArgs) error {
if err := validateFunctionShape(args.Shape); err != nil {
return err
}
if err := validateResourcesNoMax(args.Resources); err != nil {
return err
}
return nil
var path *field.Path
var allErrs field.ErrorList
allErrs = append(allErrs, validateFunctionShape(args.Shape, path.Child("shape"))...)
allErrs = append(allErrs, validateResourcesNoMax(args.Resources, path.Child("resources"))...)
return allErrs.ToAggregate()
}
func validateFunctionShape(shape []config.UtilizationShapePoint) error {
func validateFunctionShape(shape []config.UtilizationShapePoint, path *field.Path) field.ErrorList {
const (
minUtilization = 0
maxUtilization = 100
@ -199,64 +210,68 @@ func validateFunctionShape(shape []config.UtilizationShapePoint) error {
maxScore = int32(config.MaxCustomPriorityScore)
)
var allErrs field.ErrorList
if len(shape) == 0 {
return fmt.Errorf("at least one point must be specified")
allErrs = append(allErrs, field.Required(path, "at least one point must be specified"))
return allErrs
}
for i := 1; i < len(shape); i++ {
if shape[i-1].Utilization >= shape[i].Utilization {
return fmt.Errorf("utilization values must be sorted. Utilization[%d]==%d >= Utilization[%d]==%d", i-1, shape[i-1].Utilization, i, shape[i].Utilization)
allErrs = append(allErrs, field.Invalid(path.Index(i).Child("utilization"), shape[i].Utilization, "utilization values must be sorted in increasing order"))
break
}
}
for i, point := range shape {
if point.Utilization < minUtilization {
return fmt.Errorf("utilization values must not be less than %d. Utilization[%d]==%d", minUtilization, i, point.Utilization)
if point.Utilization < minUtilization || point.Utilization > maxUtilization {
msg := fmt.Sprintf("not in valid range [%d, %d]", minUtilization, maxUtilization)
allErrs = append(allErrs, field.Invalid(path.Index(i).Child("utilization"), point.Utilization, msg))
}
if point.Utilization > maxUtilization {
return fmt.Errorf("utilization values must not be greater than %d. Utilization[%d]==%d", maxUtilization, i, point.Utilization)
}
if point.Score < minScore {
return fmt.Errorf("score values must not be less than %d. Score[%d]==%d", minScore, i, point.Score)
}
if point.Score > maxScore {
return fmt.Errorf("score values must not be greater than %d. Score[%d]==%d", maxScore, i, point.Score)
if point.Score < minScore || point.Score > maxScore {
msg := fmt.Sprintf("not in valid range [%d, %d]", minScore, maxScore)
allErrs = append(allErrs, field.Invalid(path.Index(i).Child("score"), point.Score, msg))
}
}
return nil
return allErrs
}
// TODO potentially replace with validateResources
func validateResourcesNoMax(resources []config.ResourceSpec) error {
for _, r := range resources {
func validateResourcesNoMax(resources []config.ResourceSpec, p *field.Path) field.ErrorList {
var allErrs field.ErrorList
for i, r := range resources {
if r.Weight < 1 {
return fmt.Errorf("resource %s weight %d must not be less than 1", string(r.Name), r.Weight)
allErrs = append(allErrs, field.Invalid(p.Index(i).Child("weight"), r.Weight,
fmt.Sprintf("resource weight of %s not in valid range [1, inf)", r.Name)))
}
}
return nil
return allErrs
}
// ValidateNodeResourcesLeastAllocatedArgs validates that NodeResourcesLeastAllocatedArgs are correct.
func ValidateNodeResourcesLeastAllocatedArgs(args *config.NodeResourcesLeastAllocatedArgs) error {
return validateResources(args.Resources)
var path *field.Path
return validateResources(args.Resources, path.Child("resources")).ToAggregate()
}
// ValidateNodeResourcesMostAllocatedArgs validates that NodeResourcesMostAllocatedArgs are correct.
func ValidateNodeResourcesMostAllocatedArgs(args *config.NodeResourcesMostAllocatedArgs) error {
return validateResources(args.Resources)
var path *field.Path
return validateResources(args.Resources, path.Child("resources")).ToAggregate()
}
func validateResources(resources []config.ResourceSpec) error {
for _, resource := range resources {
if resource.Weight <= 0 {
return fmt.Errorf("resource Weight of %v should be a positive value, got %v", resource.Name, resource.Weight)
}
if resource.Weight > 100 {
return fmt.Errorf("resource Weight of %v should be less than 100, got %v", resource.Name, resource.Weight)
func validateResources(resources []config.ResourceSpec, p *field.Path) field.ErrorList {
var allErrs field.ErrorList
for i, resource := range resources {
if resource.Weight <= 0 || resource.Weight > 100 {
msg := fmt.Sprintf("resource weight of %v not in valid range (0, 100]", resource.Name)
allErrs = append(allErrs, field.Invalid(p.Index(i).Child("weight"), resource.Weight, msg))
}
}
return nil
return allErrs
}
// ValidateNodeAffinityArgs validates that NodeAffinityArgs are correct.

View File

@ -33,8 +33,8 @@ var (
func TestValidateDefaultPreemptionArgs(t *testing.T) {
cases := map[string]struct {
args config.DefaultPreemptionArgs
wantErr string
args config.DefaultPreemptionArgs
wantErrs field.ErrorList
}{
"valid args (default)": {
args: config.DefaultPreemptionArgs{
@ -47,35 +47,75 @@ func TestValidateDefaultPreemptionArgs(t *testing.T) {
MinCandidateNodesPercentage: -1,
MinCandidateNodesAbsolute: 100,
},
wantErr: "minCandidateNodesPercentage is not in the range [0, 100]",
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "minCandidateNodesPercentage",
},
},
},
"minCandidateNodesPercentage over 100": {
args: config.DefaultPreemptionArgs{
MinCandidateNodesPercentage: 900,
MinCandidateNodesAbsolute: 100,
},
wantErr: "minCandidateNodesPercentage is not in the range [0, 100]",
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "minCandidateNodesPercentage",
},
},
},
"negative minCandidateNodesAbsolute": {
args: config.DefaultPreemptionArgs{
MinCandidateNodesPercentage: 20,
MinCandidateNodesAbsolute: -1,
},
wantErr: "minCandidateNodesAbsolute is not in the range [0, inf)",
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "minCandidateNodesAbsolute",
},
},
},
"all zero": {
args: config.DefaultPreemptionArgs{
MinCandidateNodesPercentage: 0,
MinCandidateNodesAbsolute: 0,
},
wantErr: "both minCandidateNodesPercentage and minCandidateNodesAbsolute cannot be zero",
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "minCandidateNodesPercentage",
}, &field.Error{
Type: field.ErrorTypeInvalid,
Field: "minCandidateNodesAbsolute",
},
},
},
"both negative": {
args: config.DefaultPreemptionArgs{
MinCandidateNodesPercentage: -1,
MinCandidateNodesAbsolute: -1,
},
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "minCandidateNodesPercentage",
}, &field.Error{
Type: field.ErrorTypeInvalid,
Field: "minCandidateNodesAbsolute",
},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
err := ValidateDefaultPreemptionArgs(tc.args)
assertErr(t, tc.wantErr, err)
if diff := cmp.Diff(tc.wantErrs.ToAggregate(), err, ignoreBadValueDetail); diff != "" {
t.Fatalf("ValidateDefaultPreemptionArgs returned err (-want,+got):\n%s", diff)
}
})
}
}
@ -83,7 +123,7 @@ func TestValidateDefaultPreemptionArgs(t *testing.T) {
func TestValidateInterPodAffinityArgs(t *testing.T) {
cases := map[string]struct {
args config.InterPodAffinityArgs
wantErr string
wantErr error
}{
"valid args": {
args: config.InterPodAffinityArgs{
@ -94,28 +134,36 @@ func TestValidateInterPodAffinityArgs(t *testing.T) {
args: config.InterPodAffinityArgs{
HardPodAffinityWeight: -1,
},
wantErr: `hardPodAffinityWeight: Invalid value: -1: not in valid range [0-100]`,
wantErr: &field.Error{
Type: field.ErrorTypeInvalid,
Field: "hardPodAffinityWeight",
},
},
"hardPodAffinityWeight more than max": {
args: config.InterPodAffinityArgs{
HardPodAffinityWeight: 101,
},
wantErr: `hardPodAffinityWeight: Invalid value: 101: not in valid range [0-100]`,
wantErr: &field.Error{
Type: field.ErrorTypeInvalid,
Field: "hardPodAffinityWeight",
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
err := ValidateInterPodAffinityArgs(tc.args)
assertErr(t, tc.wantErr, err)
if diff := cmp.Diff(tc.wantErr, err, ignoreBadValueDetail); diff != "" {
t.Fatalf("ValidateInterPodAffinityArgs returned err (-want,+got):\n%s", diff)
}
})
}
}
func TestValidateNodeLabelArgs(t *testing.T) {
cases := map[string]struct {
args config.NodeLabelArgs
wantErr string
args config.NodeLabelArgs
wantErrs field.ErrorList
}{
"valid config": {
args: config.NodeLabelArgs{
@ -130,29 +178,73 @@ func TestValidateNodeLabelArgs(t *testing.T) {
PresentLabels: []string{"label"},
AbsentLabels: []string{"label"},
},
wantErr: `detecting at least one label (e.g., "label") that exist in both the present([label]) and absent([label]) label list`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "presentLabels[0]",
},
},
},
"multiple labels conflict": {
args: config.NodeLabelArgs{
PresentLabels: []string{"label", "label3"},
AbsentLabels: []string{"label", "label2", "label3"},
},
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "presentLabels[0]",
},
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "presentLabels[1]",
},
},
},
"labels preference conflict": {
args: config.NodeLabelArgs{
PresentLabelsPreference: []string{"label"},
AbsentLabelsPreference: []string{"label"},
},
wantErr: `detecting at least one label (e.g., "label") that exist in both the present([label]) and absent([label]) label list`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "presentLabelsPreference[0]",
},
},
},
"multiple labels preference conflict": {
args: config.NodeLabelArgs{
PresentLabelsPreference: []string{"label", "label3"},
AbsentLabelsPreference: []string{"label", "label2", "label3"},
},
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "presentLabelsPreference[0]",
},
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "presentLabelsPreference[1]",
},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
err := ValidateNodeLabelArgs(tc.args)
assertErr(t, tc.wantErr, err)
if diff := cmp.Diff(tc.wantErrs.ToAggregate(), err, ignoreBadValueDetail); diff != "" {
t.Fatalf("ValidateNodeLabelArgs returned err (-want,+got):\n%s", diff)
}
})
}
}
func TestValidatePodTopologySpreadArgs(t *testing.T) {
cases := map[string]struct {
args *config.PodTopologySpreadArgs
wantErr string
args *config.PodTopologySpreadArgs
wantErrs field.ErrorList
}{
"valid config": {
args: &config.PodTopologySpreadArgs{
@ -182,7 +274,12 @@ func TestValidatePodTopologySpreadArgs(t *testing.T) {
},
DefaultingType: config.ListDefaulting,
},
wantErr: `defaultConstraints[0].maxSkew: Invalid value: -1: must be greater than zero`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "defaultConstraints[0].maxSkew",
},
},
},
"empty topology key": {
args: &config.PodTopologySpreadArgs{
@ -195,7 +292,12 @@ func TestValidatePodTopologySpreadArgs(t *testing.T) {
},
DefaultingType: config.ListDefaulting,
},
wantErr: `defaultConstraints[0].topologyKey: Required value: can not be empty`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeRequired,
Field: "defaultConstraints[0].topologyKey",
},
},
},
"whenUnsatisfiable is empty": {
args: &config.PodTopologySpreadArgs{
@ -208,7 +310,12 @@ func TestValidatePodTopologySpreadArgs(t *testing.T) {
},
DefaultingType: config.ListDefaulting,
},
wantErr: `defaultConstraints[0].whenUnsatisfiable: Required value: can not be empty`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeRequired,
Field: "defaultConstraints[0].whenUnsatisfiable",
},
},
},
"whenUnsatisfiable contains unsupported action": {
args: &config.PodTopologySpreadArgs{
@ -221,7 +328,12 @@ func TestValidatePodTopologySpreadArgs(t *testing.T) {
},
DefaultingType: config.ListDefaulting,
},
wantErr: `defaultConstraints[0].whenUnsatisfiable: Unsupported value: "unknown action": supported values: "DoNotSchedule", "ScheduleAnyway"`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeNotSupported,
Field: "defaultConstraints[0].whenUnsatisfiable",
},
},
},
"duplicated constraints": {
args: &config.PodTopologySpreadArgs{
@ -239,7 +351,12 @@ func TestValidatePodTopologySpreadArgs(t *testing.T) {
},
DefaultingType: config.ListDefaulting,
},
wantErr: `defaultConstraints[1]: Duplicate value: "{node, DoNotSchedule}"`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeDuplicate,
Field: "defaultConstraints[1]",
},
},
},
"label selector present": {
args: &config.PodTopologySpreadArgs{
@ -257,7 +374,12 @@ func TestValidatePodTopologySpreadArgs(t *testing.T) {
},
DefaultingType: config.ListDefaulting,
},
wantErr: `defaultConstraints[0].labelSelector: Forbidden: constraint must not define a selector, as they deduced for each pod`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeForbidden,
Field: "defaultConstraints[0].labelSelector",
},
},
},
"list default constraints, no constraints": {
args: &config.PodTopologySpreadArgs{
@ -269,6 +391,17 @@ func TestValidatePodTopologySpreadArgs(t *testing.T) {
DefaultingType: config.SystemDefaulting,
},
},
"wrong constraints": {
args: &config.PodTopologySpreadArgs{
DefaultingType: "unknown",
},
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeNotSupported,
Field: "defaultingType",
},
},
},
"system default constraints, but has constraints": {
args: &config.PodTopologySpreadArgs{
DefaultConstraints: []v1.TopologySpreadConstraint{
@ -280,22 +413,29 @@ func TestValidatePodTopologySpreadArgs(t *testing.T) {
},
DefaultingType: config.SystemDefaulting,
},
wantErr: `defaultingType: Invalid value: "System": when .defaultConstraints are not empty`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "defaultingType",
},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
err := ValidatePodTopologySpreadArgs(tc.args)
assertErr(t, tc.wantErr, err)
if diff := cmp.Diff(tc.wantErrs.ToAggregate(), err, ignoreBadValueDetail); diff != "" {
t.Fatalf("ValidatePodTopologySpreadArgs returned err (-want,+got):\n%s", diff)
}
})
}
}
func TestValidateRequestedToCapacityRatioArgs(t *testing.T) {
cases := map[string]struct {
args config.RequestedToCapacityRatioArgs
wantErr string
args config.RequestedToCapacityRatioArgs
wantErrs field.ErrorList
}{
"valid config": {
args: config.RequestedToCapacityRatioArgs{
@ -331,7 +471,12 @@ func TestValidateRequestedToCapacityRatioArgs(t *testing.T) {
},
},
},
wantErr: `at least one point must be specified`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeRequired,
Field: "shape",
},
},
},
"utilization less than min": {
args: config.RequestedToCapacityRatioArgs{
@ -346,7 +491,12 @@ func TestValidateRequestedToCapacityRatioArgs(t *testing.T) {
},
},
},
wantErr: `utilization values must not be less than 0. Utilization[0]==-10`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "shape[0].utilization",
},
},
},
"utilization greater than max": {
args: config.RequestedToCapacityRatioArgs{
@ -361,9 +511,14 @@ func TestValidateRequestedToCapacityRatioArgs(t *testing.T) {
},
},
},
wantErr: `utilization values must not be greater than 100. Utilization[1]==110`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "shape[1].utilization",
},
},
},
"Utilization values in non-increasing order": {
"utilization values in non-increasing order": {
args: config.RequestedToCapacityRatioArgs{
Shape: []config.UtilizationShapePoint{
{
@ -380,7 +535,12 @@ func TestValidateRequestedToCapacityRatioArgs(t *testing.T) {
},
},
},
wantErr: `utilization values must be sorted. Utilization[0]==30 >= Utilization[1]==20`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "shape[1].utilization",
},
},
},
"duplicated utilization values": {
args: config.RequestedToCapacityRatioArgs{
@ -399,7 +559,12 @@ func TestValidateRequestedToCapacityRatioArgs(t *testing.T) {
},
},
},
wantErr: `utilization values must be sorted. Utilization[1]==20 >= Utilization[2]==20`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "shape[2].utilization",
},
},
},
"score less than min": {
args: config.RequestedToCapacityRatioArgs{
@ -414,7 +579,12 @@ func TestValidateRequestedToCapacityRatioArgs(t *testing.T) {
},
},
},
wantErr: `score values must not be less than 0. Score[0]==-1`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "shape[0].score",
},
},
},
"score greater than max": {
args: config.RequestedToCapacityRatioArgs{
@ -429,7 +599,12 @@ func TestValidateRequestedToCapacityRatioArgs(t *testing.T) {
},
},
},
wantErr: `score values must not be greater than 10. Score[1]==11`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "shape[1].score",
},
},
},
"resources weight less than 1": {
args: config.RequestedToCapacityRatioArgs{
@ -446,22 +621,63 @@ func TestValidateRequestedToCapacityRatioArgs(t *testing.T) {
},
},
},
wantErr: `resource custom weight 0 must not be less than 1`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[0].weight",
},
},
},
"multiple errors": {
args: config.RequestedToCapacityRatioArgs{
Shape: []config.UtilizationShapePoint{
{
Utilization: 20,
Score: -1,
},
{
Utilization: 10,
Score: 2,
},
},
Resources: []config.ResourceSpec{
{
Name: "custom",
Weight: 0,
},
},
},
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "shape[1].utilization",
},
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "shape[0].score",
},
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[0].weight",
},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
err := ValidateRequestedToCapacityRatioArgs(tc.args)
assertErr(t, tc.wantErr, err)
if diff := cmp.Diff(tc.wantErrs.ToAggregate(), err, ignoreBadValueDetail); diff != "" {
t.Fatalf("ValidateRequestedToCapacityRatioArgs returned err (-want,+got):\n%s", diff)
}
})
}
}
func TestValidateNodeResourcesLeastAllocatedArgs(t *testing.T) {
cases := map[string]struct {
args *config.NodeResourcesLeastAllocatedArgs
wantErr string
args *config.NodeResourcesLeastAllocatedArgs
wantErrs field.ErrorList
}{
"valid config": {
args: &config.NodeResourcesLeastAllocatedArgs{
@ -486,7 +702,12 @@ func TestValidateNodeResourcesLeastAllocatedArgs(t *testing.T) {
},
},
},
wantErr: `resource Weight of cpu should be a positive value, got 0`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[0].weight",
},
},
},
"weight more than max": {
args: &config.NodeResourcesLeastAllocatedArgs{
@ -497,22 +718,53 @@ func TestValidateNodeResourcesLeastAllocatedArgs(t *testing.T) {
},
},
},
wantErr: `resource Weight of memory should be less than 100, got 101`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[0].weight",
},
},
},
"multiple error": {
args: &config.NodeResourcesLeastAllocatedArgs{
Resources: []config.ResourceSpec{
{
Name: "memory",
Weight: 0,
},
{
Name: "cpu",
Weight: 101,
},
},
},
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[0].weight",
},
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[1].weight",
},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
err := ValidateNodeResourcesLeastAllocatedArgs(tc.args)
assertErr(t, tc.wantErr, err)
if diff := cmp.Diff(tc.wantErrs.ToAggregate(), err, ignoreBadValueDetail); diff != "" {
t.Fatalf("ValidateNodeResourcesLeastAllocatedArgs returned err (-want,+got):\n%s", diff)
}
})
}
}
func TestValidateNodeResourcesMostAllocatedArgs(t *testing.T) {
cases := map[string]struct {
args *config.NodeResourcesMostAllocatedArgs
wantErr string
args *config.NodeResourcesMostAllocatedArgs
wantErrs field.ErrorList
}{
"valid config": {
args: &config.NodeResourcesMostAllocatedArgs{
@ -537,7 +789,12 @@ func TestValidateNodeResourcesMostAllocatedArgs(t *testing.T) {
},
},
},
wantErr: `resource Weight of cpu should be a positive value, got -1`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[0].weight",
},
},
},
"weight more than max": {
args: &config.NodeResourcesMostAllocatedArgs{
@ -548,14 +805,45 @@ func TestValidateNodeResourcesMostAllocatedArgs(t *testing.T) {
},
},
},
wantErr: `resource Weight of memory should be less than 100, got 110`,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[0].weight",
},
},
},
"multiple error": {
args: &config.NodeResourcesMostAllocatedArgs{
Resources: []config.ResourceSpec{
{
Name: "memory",
Weight: -1,
},
{
Name: "cpu",
Weight: 110,
},
},
},
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[0].weight",
},
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[1].weight",
},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
err := ValidateNodeResourcesMostAllocatedArgs(tc.args)
assertErr(t, tc.wantErr, err)
if diff := cmp.Diff(tc.wantErrs.ToAggregate(), err, ignoreBadValueDetail); diff != "" {
t.Fatalf("ValidateNodeResourcesLeastAllocatedArgs returned err (-want,+got):\n%s", diff)
}
})
}
}
@ -651,18 +939,3 @@ func TestValidateNodeAffinityArgs(t *testing.T) {
})
}
}
func assertErr(t *testing.T, wantErr string, gotErr error) {
if wantErr == "" {
if gotErr != nil {
t.Fatalf("\nwant err to be:\n\tnil\ngot:\n\t%s", gotErr.Error())
}
} else {
if gotErr == nil {
t.Fatalf("\nwant err to be:\n\t%s\ngot:\n\tnil", wantErr)
}
if gotErr.Error() != wantErr {
t.Errorf("\nwant err to be:\n\t%s\ngot:\n\t%s", wantErr, gotErr.Error())
}
}
}

View File

@ -66,9 +66,12 @@ go_test(
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/component-base/featuregate:go_default_library",
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
"//vendor/github.com/google/go-cmp/cmp:go_default_library",
"//vendor/github.com/google/go-cmp/cmp/cmpopts:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)

View File

@ -21,9 +21,12 @@ import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/scheduler/apis/config"
"k8s.io/kubernetes/pkg/scheduler/framework"
"k8s.io/kubernetes/pkg/scheduler/framework/runtime"
@ -101,7 +104,7 @@ func TestNodeResourcesLeastAllocated(t *testing.T) {
pods []*v1.Pod
nodes []*v1.Node
args config.NodeResourcesLeastAllocatedArgs
wantErr string
wantErr error
expectedList framework.NodeScoreList
name string
}{
@ -260,27 +263,42 @@ func TestNodeResourcesLeastAllocated(t *testing.T) {
},
{
// resource with negtive weight is not allowed
pod: &v1.Pod{Spec: cpuAndMemory},
nodes: []*v1.Node{makeNode("machine", 4000, 10000)},
args: config.NodeResourcesLeastAllocatedArgs{Resources: []config.ResourceSpec{{Name: "memory", Weight: -1}, {Name: "cpu", Weight: 1}}},
wantErr: "resource Weight of memory should be a positive value, got -1",
name: "resource with negtive weight",
pod: &v1.Pod{Spec: cpuAndMemory},
nodes: []*v1.Node{makeNode("machine", 4000, 10000)},
args: config.NodeResourcesLeastAllocatedArgs{Resources: []config.ResourceSpec{{Name: "memory", Weight: -1}, {Name: "cpu", Weight: 1}}},
wantErr: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[0].weight",
},
}.ToAggregate(),
name: "resource with negtive weight",
},
{
// resource with zero weight is not allowed
pod: &v1.Pod{Spec: cpuAndMemory},
nodes: []*v1.Node{makeNode("machine", 4000, 10000)},
args: config.NodeResourcesLeastAllocatedArgs{Resources: []config.ResourceSpec{{Name: "memory", Weight: 1}, {Name: "cpu", Weight: 0}}},
wantErr: "resource Weight of cpu should be a positive value, got 0",
name: "resource with zero weight",
pod: &v1.Pod{Spec: cpuAndMemory},
nodes: []*v1.Node{makeNode("machine", 4000, 10000)},
args: config.NodeResourcesLeastAllocatedArgs{Resources: []config.ResourceSpec{{Name: "memory", Weight: 1}, {Name: "cpu", Weight: 0}}},
wantErr: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[1].weight",
},
}.ToAggregate(),
name: "resource with zero weight",
},
{
// resource weight should be less than MaxNodeScore
pod: &v1.Pod{Spec: cpuAndMemory},
nodes: []*v1.Node{makeNode("machine", 4000, 10000)},
args: config.NodeResourcesLeastAllocatedArgs{Resources: []config.ResourceSpec{{Name: "memory", Weight: 120}}},
wantErr: "resource Weight of memory should be less than 100, got 120",
name: "resource weight larger than MaxNodeScore",
pod: &v1.Pod{Spec: cpuAndMemory},
nodes: []*v1.Node{makeNode("machine", 4000, 10000)},
args: config.NodeResourcesLeastAllocatedArgs{Resources: []config.ResourceSpec{{Name: "memory", Weight: 120}}},
wantErr: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[0].weight",
},
}.ToAggregate(),
name: "resource weight larger than MaxNodeScore",
},
}
@ -290,16 +308,19 @@ func TestNodeResourcesLeastAllocated(t *testing.T) {
fh, _ := runtime.NewFramework(nil, nil, nil, runtime.WithSnapshotSharedLister(snapshot))
p, err := NewLeastAllocated(&test.args, fh)
if len(test.wantErr) != 0 {
if err != nil && test.wantErr != err.Error() {
t.Fatalf("got err %v, want %v", err.Error(), test.wantErr)
} else if err == nil {
if test.wantErr != nil {
if err != nil {
diff := cmp.Diff(test.wantErr, err, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail"))
if diff != "" {
t.Fatalf("got err (-want,+got):\n%s", diff)
}
} else {
t.Fatalf("no error produced, wanted %v", test.wantErr)
}
return
}
if err != nil && len(test.wantErr) == 0 {
if err != nil && test.wantErr == nil {
t.Fatalf("failed to initialize plugin NodeResourcesLeastAllocated, got error: %v", err)
}

View File

@ -21,9 +21,12 @@ import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/scheduler/apis/config"
"k8s.io/kubernetes/pkg/scheduler/framework"
"k8s.io/kubernetes/pkg/scheduler/framework/runtime"
@ -116,7 +119,7 @@ func TestNodeResourcesMostAllocated(t *testing.T) {
pods []*v1.Pod
nodes []*v1.Node
args config.NodeResourcesMostAllocatedArgs
wantErr string
wantErr error
expectedList framework.NodeScoreList
name string
}{
@ -220,27 +223,42 @@ func TestNodeResourcesMostAllocated(t *testing.T) {
},
{
// resource with negtive weight is not allowed
pod: &v1.Pod{Spec: cpuAndMemory},
nodes: []*v1.Node{makeNode("machine", 4000, 10000)},
args: config.NodeResourcesMostAllocatedArgs{Resources: []config.ResourceSpec{{Name: "memory", Weight: -1}, {Name: "cpu", Weight: 1}}},
wantErr: "resource Weight of memory should be a positive value, got -1",
name: "resource with negtive weight",
pod: &v1.Pod{Spec: cpuAndMemory},
nodes: []*v1.Node{makeNode("machine", 4000, 10000)},
args: config.NodeResourcesMostAllocatedArgs{Resources: []config.ResourceSpec{{Name: "memory", Weight: -1}, {Name: "cpu", Weight: 1}}},
wantErr: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[0].weight",
},
}.ToAggregate(),
name: "resource with negtive weight",
},
{
// resource with zero weight is not allowed
pod: &v1.Pod{Spec: cpuAndMemory},
nodes: []*v1.Node{makeNode("machine", 4000, 10000)},
args: config.NodeResourcesMostAllocatedArgs{Resources: []config.ResourceSpec{{Name: "memory", Weight: 1}, {Name: "cpu", Weight: 0}}},
wantErr: "resource Weight of cpu should be a positive value, got 0",
name: "resource with zero weight",
pod: &v1.Pod{Spec: cpuAndMemory},
nodes: []*v1.Node{makeNode("machine", 4000, 10000)},
args: config.NodeResourcesMostAllocatedArgs{Resources: []config.ResourceSpec{{Name: "memory", Weight: 1}, {Name: "cpu", Weight: 0}}},
wantErr: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[1].weight",
},
}.ToAggregate(),
name: "resource with zero weight",
},
{
// resource weight should be less than MaxNodeScore
pod: &v1.Pod{Spec: cpuAndMemory},
nodes: []*v1.Node{makeNode("machine", 4000, 10000)},
args: config.NodeResourcesMostAllocatedArgs{Resources: []config.ResourceSpec{{Name: "memory", Weight: 120}}},
wantErr: "resource Weight of memory should be less than 100, got 120",
name: "resource weight larger than MaxNodeScore",
pod: &v1.Pod{Spec: cpuAndMemory},
nodes: []*v1.Node{makeNode("machine", 4000, 10000)},
args: config.NodeResourcesMostAllocatedArgs{Resources: []config.ResourceSpec{{Name: "memory", Weight: 120}}},
wantErr: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "resources[0].weight",
},
}.ToAggregate(),
name: "resource weight larger than MaxNodeScore",
},
}
@ -250,16 +268,19 @@ func TestNodeResourcesMostAllocated(t *testing.T) {
fh, _ := runtime.NewFramework(nil, nil, nil, runtime.WithSnapshotSharedLister(snapshot))
p, err := NewMostAllocated(&test.args, fh)
if len(test.wantErr) != 0 {
if err != nil && test.wantErr != err.Error() {
t.Fatalf("got err %v, want %v", err.Error(), test.wantErr)
} else if err == nil {
if test.wantErr != nil {
if err != nil {
diff := cmp.Diff(test.wantErr, err, cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail"))
if diff != "" {
t.Fatalf("got err (-want,+got):\n%s", diff)
}
} else {
t.Fatalf("no error produced, wanted %v", test.wantErr)
}
return
}
if err != nil && len(test.wantErr) == 0 {
if err != nil && test.wantErr == nil {
t.Fatalf("failed to initialize plugin NodeResourcesMostAllocated, got error: %v", err)
}