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  }