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 }