knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/network/handlers/drain_test.go (about) 1 /* 2 Copyright 2020 The Knative 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 handlers 18 19 import ( 20 "net/http" 21 "net/http/httptest" 22 "net/url" 23 "testing" 24 "time" 25 26 "knative.dev/pkg/network" 27 ) 28 29 type mockTimer struct { 30 now time.Time // our current time. 31 deadline time.Time // when we're supposed to fire 32 c chan time.Time 33 resetCalls int 34 stopped bool 35 } 36 37 func (mt *mockTimer) advance(d time.Duration) { 38 mt.now = mt.now.Add(d) 39 if !mt.now.Before(mt.deadline) { 40 mt.stopped = true 41 mt.c <- mt.now 42 } 43 } 44 45 func (mt *mockTimer) Reset(d time.Duration) bool { 46 mt.resetCalls++ 47 if mt.stopped { 48 mt.now = time.Now() 49 mt.deadline = mt.now.Add(d) 50 mt.stopped = false 51 } 52 return !mt.stopped 53 } 54 55 func (mt *mockTimer) Stop() bool { 56 if mt.stopped { 57 return false 58 } 59 mt.stopped = true 60 return true 61 } 62 63 func (mt *mockTimer) tickChan() <-chan time.Time { 64 return mt.c 65 } 66 67 func TestDrainMechanics(t *testing.T) { 68 var ( 69 w http.ResponseWriter 70 req = &http.Request{} 71 probe = &http.Request{ 72 Header: http.Header{ 73 network.UserAgentKey: []string{network.KubeProbeUAPrefix}, 74 }, 75 } 76 cnt = 0 77 inner = http.HandlerFunc(func(http.ResponseWriter, *http.Request) { cnt++ }) 78 ) 79 80 const ( 81 timeout = 100 * time.Millisecond 82 epsilon = time.Nanosecond 83 ) 84 85 // We need init channel to signal the main thread that the drain 86 // has been initialized in the background thread. 87 init := make(chan struct{}) 88 nt := newTimer 89 t.Cleanup(func() { 90 newTimer = nt 91 }) 92 // The mock timer will only fire when we advance it past timeout. 93 mt := &mockTimer{ 94 c: make(chan time.Time), 95 } 96 newTimer = func(d time.Duration) timer { 97 // When we close the init channel, we know that first drain has been called, and the test can progress. 98 defer close(init) 99 mt.now = time.Now() 100 mt.deadline = mt.now.Add(d) 101 return mt 102 } 103 drainer := &Drainer{ 104 Inner: inner, 105 QuietPeriod: timeout, 106 } 107 108 // Works before Drain is called. 109 drainer.ServeHTTP(w, req) 110 drainer.ServeHTTP(w, req) 111 drainer.ServeHTTP(w, req) 112 if cnt != 3 { 113 t.Error("Inner handler was not properly invoked") 114 } 115 116 // Check for 200 OK. 117 resp := httptest.NewRecorder() 118 drainer.ServeHTTP(resp, probe) 119 if got, want := resp.Code, http.StatusOK; got != want { 120 t.Errorf("Probe status = %d, wanted %d", got, want) 121 } 122 123 // Start to drain, and close the channel when it returns. 124 done := make(chan struct{}) 125 go func() { 126 defer close(done) 127 drainer.Drain() 128 }() 129 130 select { 131 case <-done: 132 t.Error("Drain terminated prematurely.") 133 case <-init: 134 // OK. 135 } 136 mt.advance(timeout - epsilon) 137 138 // Now send a request to reset things. 139 rc := mt.resetCalls 140 drainer.ServeHTTP(w, req) 141 if mt.resetCalls != rc+1 { 142 t.Errorf("ResetCalls = %d, want: %d", mt.resetCalls, rc+1) 143 } 144 145 // Check for 503 as a probe response when shutting down. 146 resp = httptest.NewRecorder() 147 drainer.ServeHTTP(resp, probe) 148 if got, want := resp.Code, http.StatusServiceUnavailable; got != want { 149 t.Errorf("Probe status = %d, wanted %d", got, want) 150 } 151 // Verify no reset was called. 152 if got, want := mt.resetCalls, rc+1; got != want { 153 t.Errorf("ResetCalls = %d, want: %d", got, want) 154 } 155 rc++ 156 157 for i := range 3 { 158 mt.advance(timeout - epsilon) 159 select { 160 case <-done: 161 t.Error("Drain terminated prematurely.") 162 default: 163 // OK 164 } 165 // For the last one we don't want to reset the drain timer. 166 if i < 2 { 167 drainer.ServeHTTP(w, req) 168 169 // Two more drains should have been called. 170 if got, want := mt.resetCalls, rc+1; got != want { 171 t.Errorf("ResetCalls = %d, want: %d", got, want) 172 } 173 rc++ 174 } 175 } 176 177 // Probing does not reset the clock. 178 // Check for 503 on a probe when shutting down. 179 resp = httptest.NewRecorder() 180 drainer.ServeHTTP(resp, probe) 181 if got, want := resp.Code, http.StatusServiceUnavailable; got != want { 182 t.Errorf("Probe status = %d, wanted %d", got, want) 183 } 184 185 // Big finish, test that multiple invocations of Drain all block. 186 done1 := make(chan struct{}) 187 go func() { 188 defer close(done1) 189 drainer.Drain() 190 }() 191 done2 := make(chan struct{}) 192 go func() { 193 defer close(done2) 194 drainer.Drain() 195 }() 196 done3 := make(chan struct{}) 197 go func() { 198 defer close(done3) 199 drainer.Drain() 200 }() 201 202 select { 203 case <-done: 204 case <-done1: 205 case <-done2: 206 case <-done3: 207 default: 208 // Expected. 209 } 210 211 // Finally we made it there! 212 mt.advance(epsilon) 213 select { 214 case <-done: 215 case <-done1: 216 case <-done2: 217 case <-done3: 218 case <-time.After(time.Second): // We can't use default here, since it will race the tick in the drainer. 219 t.Error("Drains should have happened!") 220 } 221 222 // Check that a 4th and final one after things complete finishes instantly. 223 done4 := make(chan struct{}) 224 go func() { 225 defer close(done4) 226 drainer.Drain() 227 }() 228 229 // We need to ensure all the go routines complete, so give them ample time. 230 for idx, dch := range []chan struct{}{done, done1, done2, done3, done4} { 231 select { 232 case <-dch: 233 // Should be done. 234 case <-time.After(time.Second): 235 t.Errorf("Drain[%d] did not complete.", idx) 236 } 237 } 238 } 239 240 func TestDrainerKProbe(t *testing.T) { 241 var ( 242 w http.ResponseWriter 243 req = &http.Request{} 244 kprobehash = "hash" 245 kprobe = &http.Request{ 246 Header: http.Header{ 247 network.ProbeHeaderName: []string{network.ProbeHeaderValue}, 248 network.HashHeaderName: []string{kprobehash}, 249 }, 250 } 251 kprobeerr = &http.Request{ 252 Header: http.Header{ 253 network.ProbeHeaderName: []string{network.ProbeHeaderValue}, 254 }, 255 } 256 cnt = 0 257 inner = http.HandlerFunc(func(http.ResponseWriter, *http.Request) { cnt++ }) 258 ) 259 drainer := &Drainer{ 260 Inner: inner, 261 } 262 263 // Works before Drain is called. 264 drainer.ServeHTTP(w, req) 265 drainer.ServeHTTP(w, req) 266 drainer.ServeHTTP(w, req) 267 if cnt != 3 { 268 t.Error("Inner handler was not properly invoked") 269 } 270 271 resp := httptest.NewRecorder() 272 drainer.ServeHTTP(resp, kprobe) 273 if got, want := resp.Code, http.StatusOK; got != want { 274 t.Errorf("Probe status = %d, wanted %d", got, want) 275 } 276 277 if got, want := resp.Header().Get(network.HashHeaderName), kprobehash; got != want { 278 t.Errorf("KProbe hash = %s, wanted %s", got, want) 279 } 280 281 resp = httptest.NewRecorder() 282 drainer.ServeHTTP(resp, kprobeerr) 283 if got, want := resp.Code, http.StatusBadRequest; got != want { 284 t.Errorf("Probe status = %d, wanted %d", got, want) 285 } 286 287 if cnt != 3 { 288 t.Error("Inner handler was not properly invoked") 289 } 290 } 291 292 func TestDefaultQuietPeriod(t *testing.T) { 293 nt := newTimer 294 t.Cleanup(func() { 295 newTimer = nt 296 }) 297 mt := &mockTimer{ 298 c: make(chan time.Time), 299 } 300 init := make(chan struct{}) 301 newTimer = func(d time.Duration) timer { 302 defer close(init) 303 mt.now = time.Now() 304 mt.deadline = mt.now.Add(d) 305 return mt 306 } 307 drainer := &Drainer{ 308 Inner: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}), 309 } 310 go drainer.Drain() 311 select { 312 case <-init: 313 if got, want := mt.deadline.Sub(mt.now), network.DefaultDrainTimeout; got != want { 314 t.Errorf("DefaultDrainTimeout = %v, want: %v", got, want) 315 } 316 case <-time.After(time.Second): 317 t.Fatal("Failed to call drain in 1s") 318 } 319 mt.advance(network.DefaultDrainTimeout) 320 } 321 322 func TestHealthCheckWithProbeType(t *testing.T) { 323 tests := []struct { 324 name string 325 Header http.Header 326 UserAgents []string 327 }{{ 328 name: "with kube-probe header", 329 Header: http.Header{ 330 network.UserAgentKey: []string{network.KubeProbeUAPrefix}, 331 }, 332 UserAgents: []string{}, 333 }, { 334 name: "with extra probe header", 335 Header: http.Header{ 336 network.UserAgentKey: []string{"extra"}, 337 }, 338 UserAgents: []string{"extra"}, 339 }} 340 for _, tc := range tests { 341 t.Run(tc.name, func(t *testing.T) { 342 var ( 343 w http.ResponseWriter 344 req = &http.Request{} 345 cnt = 0 346 inner = http.HandlerFunc(func(http.ResponseWriter, *http.Request) { cnt++ }) 347 checker = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 348 if req.URL != nil && req.URL.Path == "/healthz" { 349 w.WriteHeader(http.StatusBadRequest) 350 return 351 } 352 w.WriteHeader(http.StatusAccepted) 353 }) 354 probe = &http.Request{ 355 URL: &url.URL{ 356 Path: "/healthz", 357 }, 358 Header: tc.Header, 359 } 360 ) 361 362 drainer := &Drainer{ 363 HealthCheck: checker, 364 Inner: inner, 365 HealthCheckUAPrefixes: tc.UserAgents, 366 } 367 368 // Works before Drain is called. 369 drainer.ServeHTTP(w, req) 370 drainer.ServeHTTP(w, req) 371 drainer.ServeHTTP(w, req) 372 if cnt != 3 { 373 t.Error("Inner handler was not properly invoked") 374 } 375 376 // Works for HealthCheck. 377 resp := httptest.NewRecorder() 378 drainer.ServeHTTP(resp, probe) 379 if got, want := resp.Code, http.StatusBadRequest; got != want { 380 t.Errorf("Probe status = %d, wanted %d", got, want) 381 } 382 }) 383 } 384 } 385 386 func TestIsHealthcheckRequest(t *testing.T) { 387 tests := []struct { 388 name string 389 UserAgents []string 390 request *http.Request 391 result bool 392 }{{ 393 name: "with kube-probe header", 394 UserAgents: []string{}, 395 request: &http.Request{ 396 URL: &url.URL{ 397 Path: "/healthz", 398 }, 399 Header: http.Header{ 400 network.UserAgentKey: []string{network.KubeProbeUAPrefix}, 401 }, 402 }, 403 result: true, 404 }, { 405 name: "with extra probe header", 406 UserAgents: []string{"extra"}, 407 request: &http.Request{ 408 URL: &url.URL{ 409 Path: "/healthz", 410 }, 411 Header: http.Header{ 412 network.UserAgentKey: []string{"extra"}, 413 }, 414 }, 415 result: true, 416 }, { 417 name: "without probe header", 418 UserAgents: []string{}, 419 request: &http.Request{ 420 URL: &url.URL{ 421 Path: "/healthz", 422 }, 423 Header: http.Header{ 424 network.UserAgentKey: []string{"not-a-probe"}, 425 }, 426 }, 427 result: false, 428 }} 429 for _, tc := range tests { 430 t.Run(tc.name, func(t *testing.T) { 431 d := Drainer{ 432 HealthCheckUAPrefixes: tc.UserAgents, 433 } 434 d.isHealthCheckRequest(tc.request) 435 }) 436 } 437 } 438 439 func TestIsKProbe(t *testing.T) { 440 req, err := http.NewRequest(http.MethodGet, "http://example.com/", nil) 441 if err != nil { 442 t.Fatal("Error building request:", err) 443 } 444 if isKProbe(req) { 445 t.Error("Not a knative probe but counted as such") 446 } 447 req.Header.Set(network.ProbeHeaderName, network.ProbeHeaderValue) 448 if !isKProbe(req) { 449 t.Error("knative probe but not counted as such") 450 } 451 req.Header.Del(network.ProbeHeaderName) 452 if isKProbe(req) { 453 t.Error("Not a knative probe but counted as such") 454 } 455 req.Header.Set(network.ProbeHeaderName, "no matter") 456 if isKProbe(req) { 457 t.Error("Not a knative probe but counted as such") 458 } 459 } 460 461 func TestServeKProbe(t *testing.T) { 462 var ( 463 kprobehash = "hash" 464 kprobe = &http.Request{ 465 Header: http.Header{ 466 network.ProbeHeaderName: []string{network.ProbeHeaderValue}, 467 network.HashHeaderName: []string{kprobehash}, 468 }, 469 } 470 kprobeerr = &http.Request{ 471 Header: http.Header{ 472 network.ProbeHeaderName: []string{network.ProbeHeaderValue}, 473 }, 474 } 475 ) 476 477 resp := httptest.NewRecorder() 478 serveKProbe(resp, kprobe) 479 if got, want := resp.Code, http.StatusOK; got != want { 480 t.Errorf("Probe status = %d, wanted %d", got, want) 481 } 482 483 if got, want := resp.Header().Get(network.HashHeaderName), kprobehash; got != want { 484 t.Errorf("KProbe hash = %s, wanted %s", got, want) 485 } 486 487 resp = httptest.NewRecorder() 488 serveKProbe(resp, kprobeerr) 489 if got, want := resp.Code, http.StatusBadRequest; got != want { 490 t.Errorf("Probe status = %d, wanted %d", got, want) 491 } 492 } 493 494 func TestReset(t *testing.T) { 495 d := Drainer{ 496 QuietPeriod: 5 * time.Second, 497 } 498 499 drain1 := make(chan struct{}) 500 drain2 := make(chan struct{}) 501 502 go func() { 503 defer close(drain1) 504 d.Drain() 505 }() 506 507 go func() { 508 defer close(drain2) 509 d.Drain() 510 }() 511 512 // wait for draining to be active 513 time.Sleep(50 * time.Millisecond) 514 515 d.Reset() 516 517 select { 518 case <-drain1: 519 case <-time.After(time.Second): 520 t.Fatal("Reset didn't unblock first Drain") 521 } 522 523 select { 524 case <-drain2: 525 case <-time.After(time.Second): 526 t.Fatal("Reset didn't unblock second Drain") 527 } 528 529 // Calling reset again should be a noop 530 d.Reset() 531 532 d.QuietPeriod = time.Second / 2 533 534 start := time.Now() 535 d.Drain() 536 duration := time.Since(start) 537 diff := d.QuietPeriod - duration 538 if diff < 0 { 539 diff = -diff 540 } 541 542 if diff > 50*time.Millisecond { 543 t.Error("expected to drain to wait QuietPeriod time after reset") 544 } 545 546 // Calling reset after a drain should succeed 547 d.Reset() 548 } 549 550 // https://github.com/knative/pkg/issues/2642 551 func TestResetWithActiveRequests(t *testing.T) { 552 d := Drainer{ 553 QuietPeriod: 5 * time.Second, 554 Inner: http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}), 555 } 556 557 trafficStopped := make(chan struct{}) 558 trafficStarted := make(chan struct{}) 559 drainStarted := make(chan struct{}) 560 defer close(trafficStopped) 561 562 go func() { 563 req, _ := http.NewRequest(http.MethodGet, "knative.dev", nil) 564 rec := httptest.NewRecorder() 565 566 close(trafficStarted) 567 for { 568 select { 569 case <-trafficStopped: 570 return 571 default: 572 d.ServeHTTP(rec, req) 573 } 574 } 575 }() 576 577 go func() { 578 <-trafficStarted 579 close(drainStarted) 580 d.Drain() 581 }() 582 583 <-drainStarted 584 d.Reset() 585 586 // We need requests to be active for a bit 587 time.Sleep(time.Second) 588 }