k8s.io/apiserver@v0.31.1/pkg/util/flowcontrol/request/width_test.go (about)

     1  /*
     2  Copyright 2021 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 request
    18  
    19  import (
    20  	"errors"
    21  	"net/http"
    22  	"testing"
    23  	"time"
    24  
    25  	apirequest "k8s.io/apiserver/pkg/endpoints/request"
    26  	"k8s.io/apiserver/pkg/features"
    27  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    28  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    29  )
    30  
    31  func TestWorkEstimator(t *testing.T) {
    32  	featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WatchList, true)
    33  
    34  	defaultCfg := DefaultWorkEstimatorConfig()
    35  
    36  	tests := []struct {
    37  		name                      string
    38  		requestURI                string
    39  		requestInfo               *apirequest.RequestInfo
    40  		counts                    map[string]int64
    41  		countErr                  error
    42  		watchCount                int
    43  		maxSeats                  uint64
    44  		initialSeatsExpected      uint64
    45  		finalSeatsExpected        uint64
    46  		additionalLatencyExpected time.Duration
    47  	}{
    48  		{
    49  			name:                 "request has no RequestInfo",
    50  			requestURI:           "http://server/apis/",
    51  			requestInfo:          nil,
    52  			maxSeats:             10,
    53  			initialSeatsExpected: 10,
    54  		},
    55  		{
    56  			name:       "request verb is not list",
    57  			requestURI: "http://server/apis/",
    58  			requestInfo: &apirequest.RequestInfo{
    59  				Verb: "get",
    60  			},
    61  			maxSeats:             10,
    62  			initialSeatsExpected: 1,
    63  		},
    64  		{
    65  			name:       "request verb is list, conversion to ListOptions returns error",
    66  			requestURI: "http://server/apis/foo.bar/v1/events?limit=invalid",
    67  			requestInfo: &apirequest.RequestInfo{
    68  				Verb:     "list",
    69  				APIGroup: "foo.bar",
    70  				Resource: "events",
    71  			},
    72  			counts: map[string]int64{
    73  				"events.foo.bar": 799,
    74  			},
    75  			maxSeats:             10,
    76  			initialSeatsExpected: 10,
    77  		},
    78  		{
    79  			name:       "request verb is list, has limit and resource version is 1",
    80  			requestURI: "http://server/apis/foo.bar/v1/events?limit=399&resourceVersion=1",
    81  			requestInfo: &apirequest.RequestInfo{
    82  				Verb:     "list",
    83  				APIGroup: "foo.bar",
    84  				Resource: "events",
    85  			},
    86  			counts: map[string]int64{
    87  				"events.foo.bar": 699,
    88  			},
    89  			maxSeats:             10,
    90  			initialSeatsExpected: 8,
    91  		},
    92  		{
    93  			name:       "request verb is list, limit not set",
    94  			requestURI: "http://server/apis/foo.bar/v1/events?resourceVersion=1",
    95  			requestInfo: &apirequest.RequestInfo{
    96  				Verb:     "list",
    97  				APIGroup: "foo.bar",
    98  				Resource: "events",
    99  			},
   100  			counts: map[string]int64{
   101  				"events.foo.bar": 699,
   102  			},
   103  			maxSeats:             10,
   104  			initialSeatsExpected: 7,
   105  		},
   106  		{
   107  			name:       "request verb is list, resource version not set",
   108  			requestURI: "http://server/apis/foo.bar/v1/events?limit=399",
   109  			requestInfo: &apirequest.RequestInfo{
   110  				Verb:     "list",
   111  				APIGroup: "foo.bar",
   112  				Resource: "events",
   113  			},
   114  			counts: map[string]int64{
   115  				"events.foo.bar": 699,
   116  			},
   117  			maxSeats:             10,
   118  			initialSeatsExpected: 8,
   119  		},
   120  		{
   121  			name:       "request verb is list, no query parameters, count known",
   122  			requestURI: "http://server/apis/foo.bar/v1/events",
   123  			requestInfo: &apirequest.RequestInfo{
   124  				Verb:     "list",
   125  				APIGroup: "foo.bar",
   126  				Resource: "events",
   127  			},
   128  			counts: map[string]int64{
   129  				"events.foo.bar": 399,
   130  			},
   131  			maxSeats:             10,
   132  			initialSeatsExpected: 8,
   133  		},
   134  		{
   135  			name:       "request verb is list, no query parameters, count not known",
   136  			requestURI: "http://server/apis/foo.bar/v1/events",
   137  			requestInfo: &apirequest.RequestInfo{
   138  				Verb:     "list",
   139  				APIGroup: "foo.bar",
   140  				Resource: "events",
   141  			},
   142  			countErr:             ObjectCountNotFoundErr,
   143  			maxSeats:             10,
   144  			initialSeatsExpected: 1,
   145  		},
   146  		{
   147  			name:       "request verb is list, continuation is set",
   148  			requestURI: "http://server/apis/foo.bar/v1/events?continue=token&limit=399",
   149  			requestInfo: &apirequest.RequestInfo{
   150  				Verb:     "list",
   151  				APIGroup: "foo.bar",
   152  				Resource: "events",
   153  			},
   154  			counts: map[string]int64{
   155  				"events.foo.bar": 699,
   156  			},
   157  			maxSeats:             10,
   158  			initialSeatsExpected: 8,
   159  		},
   160  		{
   161  			name:       "request verb is list, resource version is zero",
   162  			requestURI: "http://server/apis/foo.bar/v1/events?limit=299&resourceVersion=0",
   163  			requestInfo: &apirequest.RequestInfo{
   164  				Verb:     "list",
   165  				APIGroup: "foo.bar",
   166  				Resource: "events",
   167  			},
   168  			counts: map[string]int64{
   169  				"events.foo.bar": 399,
   170  			},
   171  			maxSeats:             10,
   172  			initialSeatsExpected: 4,
   173  		},
   174  		{
   175  			name:       "request verb is list, resource version is zero, no limit",
   176  			requestURI: "http://server/apis/foo.bar/v1/events?resourceVersion=0",
   177  			requestInfo: &apirequest.RequestInfo{
   178  				Verb:     "list",
   179  				APIGroup: "foo.bar",
   180  				Resource: "events",
   181  			},
   182  			counts: map[string]int64{
   183  				"events.foo.bar": 799,
   184  			},
   185  			initialSeatsExpected: 8,
   186  		},
   187  		{
   188  			name:       "request verb is list, resource version match is Exact",
   189  			requestURI: "http://server/apis/foo.bar/v1/events?resourceVersion=foo&resourceVersionMatch=Exact&limit=399",
   190  			requestInfo: &apirequest.RequestInfo{
   191  				Verb:     "list",
   192  				APIGroup: "foo.bar",
   193  				Resource: "events",
   194  			},
   195  			counts: map[string]int64{
   196  				"events.foo.bar": 699,
   197  			},
   198  			maxSeats:             10,
   199  			initialSeatsExpected: 8,
   200  		},
   201  		{
   202  			name:       "request verb is list, resource version match is NotOlderThan, limit not specified",
   203  			requestURI: "http://server/apis/foo.bar/v1/events?resourceVersion=foo&resourceVersionMatch=NotOlderThan",
   204  			requestInfo: &apirequest.RequestInfo{
   205  				Verb:     "list",
   206  				APIGroup: "foo.bar",
   207  				Resource: "events",
   208  			},
   209  			counts: map[string]int64{
   210  				"events.foo.bar": 799,
   211  			},
   212  			maxSeats:             10,
   213  			initialSeatsExpected: 8,
   214  		},
   215  		{
   216  			name:       "request verb is list, maximum is capped",
   217  			requestURI: "http://server/apis/foo.bar/v1/events?resourceVersion=foo",
   218  			requestInfo: &apirequest.RequestInfo{
   219  				Verb:     "list",
   220  				APIGroup: "foo.bar",
   221  				Resource: "events",
   222  			},
   223  			counts: map[string]int64{
   224  				"events.foo.bar": 1999,
   225  			},
   226  			maxSeats:             10,
   227  			initialSeatsExpected: 10,
   228  		},
   229  		{
   230  			name:       "request verb is list, maximum is capped, lower max seats",
   231  			requestURI: "http://server/apis/foo.bar/v1/events?resourceVersion=foo",
   232  			requestInfo: &apirequest.RequestInfo{
   233  				Verb:     "list",
   234  				APIGroup: "foo.bar",
   235  				Resource: "events",
   236  			},
   237  			counts: map[string]int64{
   238  				"events.foo.bar": 1999,
   239  			},
   240  			maxSeats:             5,
   241  			initialSeatsExpected: 5,
   242  		},
   243  		{
   244  			name:       "request verb is list, list from cache, count not known",
   245  			requestURI: "http://server/apis/foo.bar/v1/events?resourceVersion=0&limit=799",
   246  			requestInfo: &apirequest.RequestInfo{
   247  				Verb:     "list",
   248  				APIGroup: "foo.bar",
   249  				Resource: "events",
   250  			},
   251  			countErr:             ObjectCountNotFoundErr,
   252  			maxSeats:             10,
   253  			initialSeatsExpected: 1,
   254  		},
   255  		{
   256  			name:       "request verb is list, object count is stale",
   257  			requestURI: "http://server/apis/foo.bar/v1/events?limit=499",
   258  			requestInfo: &apirequest.RequestInfo{
   259  				Verb:     "list",
   260  				APIGroup: "foo.bar",
   261  				Resource: "events",
   262  			},
   263  			counts: map[string]int64{
   264  				"events.foo.bar": 799,
   265  			},
   266  			countErr:             ObjectCountStaleErr,
   267  			maxSeats:             10,
   268  			initialSeatsExpected: 10,
   269  		},
   270  		{
   271  			name:       "request verb is list, object count is not found",
   272  			requestURI: "http://server/apis/foo.bar/v1/events?limit=499",
   273  			requestInfo: &apirequest.RequestInfo{
   274  				Verb:     "list",
   275  				APIGroup: "foo.bar",
   276  				Resource: "events",
   277  			},
   278  			countErr:             ObjectCountNotFoundErr,
   279  			maxSeats:             10,
   280  			initialSeatsExpected: 1,
   281  		},
   282  		{
   283  			name:       "request verb is list, count getter throws unknown error",
   284  			requestURI: "http://server/apis/foo.bar/v1/events?limit=499",
   285  			requestInfo: &apirequest.RequestInfo{
   286  				Verb:     "list",
   287  				APIGroup: "foo.bar",
   288  				Resource: "events",
   289  			},
   290  			countErr:             errors.New("unknown error"),
   291  			maxSeats:             10,
   292  			initialSeatsExpected: 10,
   293  		},
   294  		{
   295  			name:       "request verb is list, metadata.name specified",
   296  			requestURI: "http://server/apis/foo.bar/v1/events?fieldSelector=metadata.name%3Dtest",
   297  			requestInfo: &apirequest.RequestInfo{
   298  				Verb:     "list",
   299  				Name:     "test",
   300  				APIGroup: "foo.bar",
   301  				Resource: "events",
   302  			},
   303  			counts: map[string]int64{
   304  				"events.foo.bar": 799,
   305  			},
   306  			maxSeats:             10,
   307  			initialSeatsExpected: 1,
   308  		},
   309  		{
   310  			name:       "request verb is list, metadata.name, resourceVersion and limit specified",
   311  			requestURI: "http://server/apis/foo.bar/v1/events?fieldSelector=metadata.name%3Dtest&limit=500&resourceVersion=0",
   312  			requestInfo: &apirequest.RequestInfo{
   313  				Verb:     "list",
   314  				Name:     "test",
   315  				APIGroup: "foo.bar",
   316  				Resource: "events",
   317  			},
   318  			counts: map[string]int64{
   319  				"events.foo.bar": 799,
   320  			},
   321  			maxSeats:             10,
   322  			initialSeatsExpected: 1,
   323  		},
   324  		{
   325  			name:       "request verb is watch, sendInitialEvents is nil",
   326  			requestURI: "http://server/apis/foo.bar/v1/events?watch=true",
   327  			requestInfo: &apirequest.RequestInfo{
   328  				Verb:     "watch",
   329  				APIGroup: "foo.bar",
   330  				Resource: "events",
   331  			},
   332  			counts: map[string]int64{
   333  				"events.foo.bar": 799,
   334  			},
   335  			initialSeatsExpected: minimumSeats,
   336  		},
   337  		{
   338  			name:       "request verb is watch, sendInitialEvents is false",
   339  			requestURI: "http://server/apis/foo.bar/v1/events?watch=true&sendInitialEvents=false",
   340  			requestInfo: &apirequest.RequestInfo{
   341  				Verb:     "watch",
   342  				APIGroup: "foo.bar",
   343  				Resource: "events",
   344  			},
   345  			counts: map[string]int64{
   346  				"events.foo.bar": 799,
   347  			},
   348  			initialSeatsExpected: minimumSeats,
   349  		},
   350  		{
   351  			name:       "request verb is watch, sendInitialEvents is true",
   352  			requestURI: "http://server/apis/foo.bar/v1/events?watch=true&sendInitialEvents=true",
   353  			requestInfo: &apirequest.RequestInfo{
   354  				Verb:     "watch",
   355  				APIGroup: "foo.bar",
   356  				Resource: "events",
   357  			},
   358  			counts: map[string]int64{
   359  				"events.foo.bar": 799,
   360  			},
   361  			initialSeatsExpected: 8,
   362  		},
   363  		{
   364  			name:       "request verb is create, no watches",
   365  			requestURI: "http://server/apis/foo.bar/v1/foos",
   366  			requestInfo: &apirequest.RequestInfo{
   367  				Verb:     "create",
   368  				APIGroup: "foo.bar",
   369  				Resource: "foos",
   370  			},
   371  			maxSeats:                  10,
   372  			initialSeatsExpected:      1,
   373  			finalSeatsExpected:        0,
   374  			additionalLatencyExpected: 0,
   375  		},
   376  		{
   377  			name:       "request verb is create, watches registered",
   378  			requestURI: "http://server/apis/foo.bar/v1/foos",
   379  			requestInfo: &apirequest.RequestInfo{
   380  				Verb:     "create",
   381  				APIGroup: "foo.bar",
   382  				Resource: "foos",
   383  			},
   384  			watchCount:                29,
   385  			maxSeats:                  10,
   386  			initialSeatsExpected:      1,
   387  			finalSeatsExpected:        3,
   388  			additionalLatencyExpected: 5 * time.Millisecond,
   389  		},
   390  		{
   391  			name:       "request verb is create, watches registered, no additional latency",
   392  			requestURI: "http://server/apis/foo.bar/v1/foos",
   393  			requestInfo: &apirequest.RequestInfo{
   394  				Verb:     "create",
   395  				APIGroup: "foo.bar",
   396  				Resource: "foos",
   397  			},
   398  			watchCount:                5,
   399  			maxSeats:                  10,
   400  			initialSeatsExpected:      1,
   401  			finalSeatsExpected:        0,
   402  			additionalLatencyExpected: 0,
   403  		},
   404  		{
   405  			name:       "request verb is create, watches registered, maximum is capped",
   406  			requestURI: "http://server/apis/foo.bar/v1/foos",
   407  			requestInfo: &apirequest.RequestInfo{
   408  				Verb:     "create",
   409  				APIGroup: "foo.bar",
   410  				Resource: "foos",
   411  			},
   412  			watchCount:                199,
   413  			maxSeats:                  10,
   414  			initialSeatsExpected:      1,
   415  			finalSeatsExpected:        10,
   416  			additionalLatencyExpected: 10 * time.Millisecond,
   417  		},
   418  		{
   419  			name:       "request verb is update, no watches",
   420  			requestURI: "http://server/apis/foo.bar/v1/foos/myfoo",
   421  			requestInfo: &apirequest.RequestInfo{
   422  				Verb:     "update",
   423  				APIGroup: "foo.bar",
   424  				Resource: "foos",
   425  			},
   426  			maxSeats:                  10,
   427  			initialSeatsExpected:      1,
   428  			finalSeatsExpected:        0,
   429  			additionalLatencyExpected: 0,
   430  		},
   431  		{
   432  			name:       "request verb is update, watches registered",
   433  			requestURI: "http://server/apis/foor.bar/v1/foos/myfoo",
   434  			requestInfo: &apirequest.RequestInfo{
   435  				Verb:     "update",
   436  				APIGroup: "foo.bar",
   437  				Resource: "foos",
   438  			},
   439  			watchCount:                29,
   440  			maxSeats:                  10,
   441  			initialSeatsExpected:      1,
   442  			finalSeatsExpected:        3,
   443  			additionalLatencyExpected: 5 * time.Millisecond,
   444  		},
   445  		{
   446  			name:       "request verb is patch, no watches",
   447  			requestURI: "http://server/apis/foo.bar/v1/foos/myfoo",
   448  			requestInfo: &apirequest.RequestInfo{
   449  				Verb:     "patch",
   450  				APIGroup: "foo.bar",
   451  				Resource: "foos",
   452  			},
   453  			maxSeats:                  10,
   454  			initialSeatsExpected:      1,
   455  			finalSeatsExpected:        0,
   456  			additionalLatencyExpected: 0,
   457  		},
   458  		{
   459  			name:       "request verb is patch, watches registered",
   460  			requestURI: "http://server/apis/foo.bar/v1/foos/myfoo",
   461  			requestInfo: &apirequest.RequestInfo{
   462  				Verb:     "patch",
   463  				APIGroup: "foo.bar",
   464  				Resource: "foos",
   465  			},
   466  			watchCount:                29,
   467  			maxSeats:                  10,
   468  			initialSeatsExpected:      1,
   469  			finalSeatsExpected:        3,
   470  			additionalLatencyExpected: 5 * time.Millisecond,
   471  		},
   472  		{
   473  			name:       "request verb is patch, watches registered, lower max seats",
   474  			requestURI: "http://server/apis/foo.bar/v1/foos/myfoo",
   475  			requestInfo: &apirequest.RequestInfo{
   476  				Verb:     "patch",
   477  				APIGroup: "foo.bar",
   478  				Resource: "foos",
   479  			},
   480  			watchCount:                100,
   481  			maxSeats:                  5,
   482  			initialSeatsExpected:      1,
   483  			finalSeatsExpected:        5,
   484  			additionalLatencyExpected: 10 * time.Millisecond,
   485  		},
   486  		{
   487  			name:       "request verb is delete, no watches",
   488  			requestURI: "http://server/apis/foo.bar/v1/foos/myfoo",
   489  			requestInfo: &apirequest.RequestInfo{
   490  				Verb:     "delete",
   491  				APIGroup: "foo.bar",
   492  				Resource: "foos",
   493  			},
   494  			maxSeats:                  10,
   495  			initialSeatsExpected:      1,
   496  			finalSeatsExpected:        0,
   497  			additionalLatencyExpected: 0,
   498  		},
   499  		{
   500  			name:       "request verb is delete, watches registered",
   501  			requestURI: "http://server/apis/foo.bar/v1/foos/myfoo",
   502  			requestInfo: &apirequest.RequestInfo{
   503  				Verb:     "delete",
   504  				APIGroup: "foo.bar",
   505  				Resource: "foos",
   506  			},
   507  			watchCount:                29,
   508  			maxSeats:                  10,
   509  			initialSeatsExpected:      1,
   510  			finalSeatsExpected:        3,
   511  			additionalLatencyExpected: 5 * time.Millisecond,
   512  		},
   513  		{
   514  			name:       "creating token for service account",
   515  			requestURI: "http://server/api/v1/namespaces/foo/serviceaccounts/default/token",
   516  			requestInfo: &apirequest.RequestInfo{
   517  				Verb:        "create",
   518  				APIGroup:    "v1",
   519  				Resource:    "serviceaccounts",
   520  				Subresource: "token",
   521  			},
   522  			watchCount:                5777,
   523  			maxSeats:                  10,
   524  			initialSeatsExpected:      1,
   525  			finalSeatsExpected:        0,
   526  			additionalLatencyExpected: 0,
   527  		},
   528  		{
   529  			name:       "creating service account",
   530  			requestURI: "http://server/api/v1/namespaces/foo/serviceaccounts",
   531  			requestInfo: &apirequest.RequestInfo{
   532  				Verb:     "create",
   533  				APIGroup: "v1",
   534  				Resource: "serviceaccounts",
   535  			},
   536  			watchCount:                1000,
   537  			maxSeats:                  10,
   538  			initialSeatsExpected:      1,
   539  			finalSeatsExpected:        10,
   540  			additionalLatencyExpected: 50 * time.Millisecond,
   541  		},
   542  	}
   543  
   544  	for _, test := range tests {
   545  		t.Run(test.name, func(t *testing.T) {
   546  			counts := test.counts
   547  			if len(counts) == 0 {
   548  				counts = map[string]int64{}
   549  			}
   550  			countsFn := func(key string) (int64, error) {
   551  				return counts[key], test.countErr
   552  			}
   553  			watchCountsFn := func(_ *apirequest.RequestInfo) int {
   554  				return test.watchCount
   555  			}
   556  			maxSeatsFn := func(_ string) uint64 {
   557  				return test.maxSeats
   558  			}
   559  
   560  			estimator := NewWorkEstimator(countsFn, watchCountsFn, defaultCfg, maxSeatsFn)
   561  
   562  			req, err := http.NewRequest("GET", test.requestURI, nil)
   563  			if err != nil {
   564  				t.Fatalf("Failed to create new HTTP request - %v", err)
   565  			}
   566  
   567  			if test.requestInfo != nil {
   568  				req = req.WithContext(apirequest.WithRequestInfo(req.Context(), test.requestInfo))
   569  			}
   570  
   571  			workestimateGot := estimator.EstimateWork(req, "testFS", "testPL")
   572  			if test.initialSeatsExpected != workestimateGot.InitialSeats {
   573  				t.Errorf("Expected work estimate to match: %d initial seats, but got: %d", test.initialSeatsExpected, workestimateGot.InitialSeats)
   574  			}
   575  			if test.finalSeatsExpected != workestimateGot.FinalSeats {
   576  				t.Errorf("Expected work estimate to match: %d final seats, but got: %d", test.finalSeatsExpected, workestimateGot.FinalSeats)
   577  			}
   578  			if test.additionalLatencyExpected != workestimateGot.AdditionalLatency {
   579  				t.Errorf("Expected work estimate to match additional latency: %v, but got: %v", test.additionalLatencyExpected, workestimateGot.AdditionalLatency)
   580  			}
   581  		})
   582  	}
   583  }