k8s.io/kubernetes@v1.29.3/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy_test.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package prioritylevelconfiguration
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	flowcontrolv1 "k8s.io/api/flowcontrol/v1"
    24  	flowcontrolv1beta3 "k8s.io/api/flowcontrol/v1beta3"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/util/validation/field"
    28  	"k8s.io/apiserver/pkg/features"
    29  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    30  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    31  	"k8s.io/kubernetes/pkg/apis/flowcontrol"
    32  	"k8s.io/utils/ptr"
    33  
    34  	"github.com/google/go-cmp/cmp"
    35  )
    36  
    37  func TestPriorityLevelConfigurationValidation(t *testing.T) {
    38  	v1ObjFn := func(v *int32) *flowcontrolv1.PriorityLevelConfiguration {
    39  		return &flowcontrolv1.PriorityLevelConfiguration{
    40  			ObjectMeta: metav1.ObjectMeta{
    41  				Name: "foo",
    42  			},
    43  			Spec: flowcontrolv1.PriorityLevelConfigurationSpec{
    44  				Type: flowcontrolv1.PriorityLevelEnablementLimited,
    45  				Limited: &flowcontrolv1.LimitedPriorityLevelConfiguration{
    46  					NominalConcurrencyShares: v,
    47  					LimitResponse: flowcontrolv1.LimitResponse{
    48  						Type: flowcontrolv1.LimitResponseTypeReject},
    49  				},
    50  			},
    51  		}
    52  	}
    53  	v1beta3ObjFn := func(v int32, isZero bool) *flowcontrolv1beta3.PriorityLevelConfiguration {
    54  		obj := &flowcontrolv1beta3.PriorityLevelConfiguration{
    55  			ObjectMeta: metav1.ObjectMeta{
    56  				Name: "foo",
    57  			},
    58  			Spec: flowcontrolv1beta3.PriorityLevelConfigurationSpec{
    59  				Type: flowcontrolv1beta3.PriorityLevelEnablementLimited,
    60  				Limited: &flowcontrolv1beta3.LimitedPriorityLevelConfiguration{
    61  					NominalConcurrencyShares: v,
    62  					LimitResponse: flowcontrolv1beta3.LimitResponse{
    63  						Type: flowcontrolv1beta3.LimitResponseTypeReject},
    64  				},
    65  			},
    66  		}
    67  		if isZero && v == 0 {
    68  			obj.ObjectMeta.Annotations = map[string]string{}
    69  			obj.ObjectMeta.Annotations[flowcontrolv1beta3.PriorityLevelPreserveZeroConcurrencySharesKey] = ""
    70  		}
    71  		return obj
    72  	}
    73  	internalObjFn := func(v int32) *flowcontrol.PriorityLevelConfiguration {
    74  		return &flowcontrol.PriorityLevelConfiguration{
    75  			ObjectMeta: metav1.ObjectMeta{
    76  				Name: "foo",
    77  			},
    78  			Spec: flowcontrol.PriorityLevelConfigurationSpec{
    79  				Type: flowcontrol.PriorityLevelEnablementLimited,
    80  				Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
    81  					NominalConcurrencyShares: v,
    82  					LimitResponse: flowcontrol.LimitResponse{
    83  						Type: flowcontrol.LimitResponseTypeReject},
    84  				},
    85  			},
    86  		}
    87  	}
    88  	v1SchemeFn := func(t *testing.T) *runtime.Scheme {
    89  		scheme := runtime.NewScheme()
    90  		if err := flowcontrolv1.AddToScheme(scheme); err != nil {
    91  			t.Fatalf("Failed to add to scheme: %v", err)
    92  		}
    93  		return scheme
    94  	}
    95  	v1beta3SchemeFn := func(t *testing.T) *runtime.Scheme {
    96  		scheme := runtime.NewScheme()
    97  		if err := flowcontrolv1beta3.AddToScheme(scheme); err != nil {
    98  			t.Fatalf("Failed to add to scheme: %v", err)
    99  		}
   100  		return scheme
   101  	}
   102  	errExpectedFn := func(v int32, msg string) field.ErrorList {
   103  		return field.ErrorList{
   104  			field.Invalid(field.NewPath("spec").Child("limited").Child("nominalConcurrencyShares"), int32(v), msg),
   105  		}
   106  	}
   107  
   108  	tests := []struct {
   109  		name               string
   110  		obj                runtime.Object
   111  		old                *flowcontrol.PriorityLevelConfiguration // for UPDATE only
   112  		zeroFeatureEnabled bool
   113  		scheme             *runtime.Scheme
   114  		errExpected        field.ErrorList
   115  	}{
   116  		{
   117  			name:               "v1, feature disabled, create, zero value, error expected",
   118  			obj:                v1ObjFn(ptr.To(int32(0))),
   119  			zeroFeatureEnabled: false,
   120  			scheme:             v1SchemeFn(t),
   121  			errExpected:        errExpectedFn(0, "must be positive"),
   122  		},
   123  		{
   124  			name:               "v1, feature disabled, create, unset, no error expected",
   125  			obj:                v1ObjFn(nil),
   126  			zeroFeatureEnabled: false,
   127  			scheme:             v1SchemeFn(t),
   128  			errExpected:        nil,
   129  		},
   130  		{
   131  			name:               "v1, feature disabled, create, non-zero, no error expected",
   132  			obj:                v1ObjFn(ptr.To(int32(1))),
   133  			zeroFeatureEnabled: false,
   134  			scheme:             v1SchemeFn(t),
   135  			errExpected:        nil,
   136  		},
   137  		{
   138  			name:               "v1, feature enabled, create, zero value, no error expected",
   139  			obj:                v1ObjFn(ptr.To(int32(0))),
   140  			zeroFeatureEnabled: true,
   141  			scheme:             v1SchemeFn(t),
   142  			errExpected:        nil,
   143  		},
   144  		{
   145  			name:               "v1, feature enabled, create, unset, no error expected",
   146  			obj:                v1ObjFn(nil),
   147  			zeroFeatureEnabled: true,
   148  			scheme:             v1SchemeFn(t),
   149  			errExpected:        nil,
   150  		},
   151  		{
   152  			name:               "v1, feature enabled, create, non-zero, no error expected",
   153  			obj:                v1ObjFn(ptr.To(int32(1))),
   154  			zeroFeatureEnabled: true,
   155  			scheme:             v1SchemeFn(t),
   156  			errExpected:        nil,
   157  		},
   158  		{
   159  			name:               "v1beta3, feature disabled, create, zero value, error expected",
   160  			obj:                v1beta3ObjFn(0, true),
   161  			zeroFeatureEnabled: false,
   162  			scheme:             v1beta3SchemeFn(t),
   163  			errExpected:        errExpectedFn(0, "must be positive"),
   164  		},
   165  		{
   166  			name:               "v1beta3, feature disabled, create, zero value without annotation, no error expected",
   167  			obj:                v1beta3ObjFn(0, false),
   168  			zeroFeatureEnabled: false,
   169  			scheme:             v1beta3SchemeFn(t),
   170  			errExpected:        nil,
   171  		},
   172  		{
   173  			name:               "v1beta3, feature disabled, create, non-zero, no error expected",
   174  			obj:                v1beta3ObjFn(1, false),
   175  			zeroFeatureEnabled: false,
   176  			scheme:             v1beta3SchemeFn(t),
   177  			errExpected:        nil,
   178  		},
   179  		{
   180  			name:               "v1beta3, feature enabled, create, zero value, no error expected",
   181  			obj:                v1beta3ObjFn(0, true),
   182  			zeroFeatureEnabled: true,
   183  			scheme:             v1beta3SchemeFn(t),
   184  			errExpected:        nil,
   185  		},
   186  		{
   187  			name:               "v1beta3, feature enabled, create, zero value without annotation, no error expected",
   188  			obj:                v1beta3ObjFn(0, false),
   189  			zeroFeatureEnabled: true,
   190  			scheme:             v1beta3SchemeFn(t),
   191  			errExpected:        nil,
   192  		},
   193  		{
   194  			name:               "v1beta3, feature enabled, create, non-zero, no error expected",
   195  			obj:                v1beta3ObjFn(1, false),
   196  			zeroFeatureEnabled: true,
   197  			scheme:             v1beta3SchemeFn(t),
   198  			errExpected:        nil,
   199  		},
   200  
   201  		// the following use cases cover UPDATE
   202  		{
   203  			name:               "v1, feature disabled, update, zero value, existing has non-zero, error expected",
   204  			obj:                v1ObjFn(ptr.To(int32(0))),
   205  			old:                internalObjFn(1),
   206  			zeroFeatureEnabled: false,
   207  			scheme:             v1SchemeFn(t),
   208  			errExpected:        errExpectedFn(0, "must be positive"),
   209  		},
   210  		{
   211  			name:               "v1, feature disabled, update, zero value, existing has zero, no error expected",
   212  			obj:                v1ObjFn(ptr.To(int32(0))),
   213  			old:                internalObjFn(0),
   214  			zeroFeatureEnabled: false,
   215  			scheme:             v1SchemeFn(t),
   216  			errExpected:        nil,
   217  		},
   218  		{
   219  			name:               "v1, feature disabled, update, non-zero value, existing has zero, no error expected",
   220  			obj:                v1ObjFn(ptr.To(int32(1))),
   221  			old:                internalObjFn(0),
   222  			zeroFeatureEnabled: false,
   223  			scheme:             v1SchemeFn(t),
   224  			errExpected:        nil,
   225  		},
   226  		{
   227  			name:               "v1, feature enabled, update, zero value, existing has non-zero, no error expected",
   228  			obj:                v1ObjFn(ptr.To(int32(0))),
   229  			old:                internalObjFn(1),
   230  			zeroFeatureEnabled: true,
   231  			scheme:             v1SchemeFn(t),
   232  			errExpected:        nil,
   233  		},
   234  		{
   235  			name:               "v1, feature enabled, update, zero value, existing has zero, no error expected",
   236  			obj:                v1ObjFn(ptr.To(int32(0))),
   237  			old:                internalObjFn(0),
   238  			zeroFeatureEnabled: true,
   239  			scheme:             v1SchemeFn(t),
   240  			errExpected:        nil,
   241  		},
   242  		{
   243  			name:               "v1, feature enabled, update, non-zero value, existing has zero, no error expected",
   244  			obj:                v1ObjFn(ptr.To(int32(1))),
   245  			old:                internalObjFn(0),
   246  			zeroFeatureEnabled: true,
   247  			scheme:             v1SchemeFn(t),
   248  			errExpected:        nil,
   249  		},
   250  		{
   251  			name:               "v1beta3, feature disabled, update, zero value, existing has non-zero, error expected",
   252  			obj:                v1beta3ObjFn(0, true),
   253  			old:                internalObjFn(1),
   254  			zeroFeatureEnabled: false,
   255  			scheme:             v1beta3SchemeFn(t),
   256  			errExpected:        errExpectedFn(0, "must be positive"),
   257  		},
   258  		{
   259  			name:               "v1beta3, feature disabled, update, zero value, existing has zero, no error expected",
   260  			obj:                v1beta3ObjFn(0, true),
   261  			old:                internalObjFn(0),
   262  			zeroFeatureEnabled: false,
   263  			scheme:             v1beta3SchemeFn(t),
   264  			errExpected:        nil,
   265  		},
   266  		{
   267  			name:               "v1beta3, feature disabled, update, non-zero value, existing has zero, no error expected",
   268  			obj:                v1beta3ObjFn(1, false),
   269  			old:                internalObjFn(0),
   270  			zeroFeatureEnabled: false,
   271  			scheme:             v1beta3SchemeFn(t),
   272  			errExpected:        nil,
   273  		},
   274  		{
   275  			name:               "v1beta3, feature enabled, update, zero value, existing has non-zero, no error expected",
   276  			obj:                v1beta3ObjFn(0, true),
   277  			old:                internalObjFn(1),
   278  			zeroFeatureEnabled: true,
   279  			scheme:             v1beta3SchemeFn(t),
   280  			errExpected:        nil,
   281  		},
   282  		{
   283  			name:               "v1beta3, feature enabled, update, zero value, existing has zero, no error expected",
   284  			obj:                v1beta3ObjFn(0, true),
   285  			old:                internalObjFn(0),
   286  			zeroFeatureEnabled: true,
   287  			scheme:             v1beta3SchemeFn(t),
   288  			errExpected:        nil,
   289  		},
   290  		{
   291  			name:               "v1beta3, feature enabled, update, non-zero value, existing has zero, no error expected",
   292  			obj:                v1beta3ObjFn(1, false),
   293  			old:                internalObjFn(0),
   294  			zeroFeatureEnabled: true,
   295  			scheme:             v1beta3SchemeFn(t),
   296  			errExpected:        nil,
   297  		},
   298  	}
   299  
   300  	for _, test := range tests {
   301  		t.Run(test.name, func(t *testing.T) {
   302  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ZeroLimitedNominalConcurrencyShares, test.zeroFeatureEnabled)()
   303  
   304  			scheme := test.scheme
   305  			scheme.Default(test.obj)
   306  
   307  			ctx := context.TODO()
   308  			internal := &flowcontrol.PriorityLevelConfiguration{}
   309  			if err := scheme.Convert(test.obj, internal, ctx); err != nil {
   310  				t.Errorf("Expected no error while converting to internal type: %v", err)
   311  			}
   312  
   313  			err := func(obj, old *flowcontrol.PriorityLevelConfiguration) field.ErrorList {
   314  				if old == nil {
   315  					return Strategy.Validate(ctx, obj) // for create operation
   316  				}
   317  				return Strategy.ValidateUpdate(ctx, obj, old) // for update operation
   318  			}(internal, test.old)
   319  
   320  			if !cmp.Equal(test.errExpected, err) {
   321  				t.Errorf("Expected error: %v, diff: %s", test.errExpected, cmp.Diff(test.errExpected, err))
   322  			}
   323  		})
   324  	}
   325  }