knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/network/handlers/drain.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  	"fmt"
    21  	"net/http"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"knative.dev/pkg/network"
    27  )
    28  
    29  type timer interface {
    30  	Stop() bool
    31  	Reset(time.Duration) bool
    32  	tickChan() <-chan time.Time
    33  }
    34  
    35  type sysTimer struct {
    36  	*time.Timer
    37  }
    38  
    39  func (s *sysTimer) tickChan() <-chan time.Time {
    40  	return s.Timer.C
    41  }
    42  
    43  // This constructor is overridden in tests to control the progress
    44  // of time in the test.
    45  var newTimer = func(d time.Duration) timer {
    46  	return &sysTimer{
    47  		time.NewTimer(d),
    48  	}
    49  }
    50  
    51  // Drainer wraps an inner http.Handler to support responding to kubelet
    52  // probes and KProbes with a "200 OK" until the handler is told to Drain,
    53  // or Drainer will optionally run the HealthCheck if it is defined.
    54  // When the Drainer is told to Drain, it will immediately start to fail
    55  // probes with a "500 shutting down", and the call will block until no
    56  // requests have been received for QuietPeriod (defaults to
    57  // network.DefaultDrainTimeout).
    58  type Drainer struct {
    59  	// Mutex guards the initialization and resets of the timer
    60  	sync.RWMutex
    61  
    62  	// HealthCheck is an optional health check that is performed until the drain signal is received.
    63  	// When unspecified, a "200 OK" is returned, otherwise this function is invoked.
    64  	HealthCheck http.HandlerFunc
    65  
    66  	// Inner is the http.Handler to which we delegate actual requests.
    67  	Inner http.Handler
    68  
    69  	// QuietPeriod is the duration that must elapse without any requests
    70  	// after Drain is called before it may return.
    71  	QuietPeriod time.Duration
    72  
    73  	// timer is used to orchestrate the drain.
    74  	timer timer
    75  
    76  	// used to synchronize callers of Drain
    77  	drainCh chan struct{}
    78  
    79  	// used to synchronize Drain and Reset
    80  	resetCh chan struct{}
    81  
    82  	// HealthCheckUAPrefixes are the additional user agent prefixes that trigger the
    83  	// drainer's health check
    84  	HealthCheckUAPrefixes []string
    85  }
    86  
    87  // Ensure Drainer implements http.Handler
    88  var _ http.Handler = (*Drainer)(nil)
    89  
    90  // ServeHTTP implements http.Handler
    91  func (d *Drainer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    92  	// Respond to probes regardless of path.
    93  	if d.isHealthCheckRequest(r) {
    94  		if d.draining() {
    95  			http.Error(w, "shutting down", http.StatusServiceUnavailable)
    96  		} else if d.HealthCheck != nil {
    97  			d.HealthCheck(w, r)
    98  		} else {
    99  			w.WriteHeader(http.StatusOK)
   100  		}
   101  		return
   102  	}
   103  	if isKProbe(r) {
   104  		if d.draining() {
   105  			http.Error(w, "shutting down", http.StatusServiceUnavailable)
   106  		} else {
   107  			serveKProbe(w, r)
   108  		}
   109  		return
   110  	}
   111  
   112  	d.resetTimer()
   113  	d.Inner.ServeHTTP(w, r)
   114  }
   115  
   116  // Drain blocks until QuietPeriod has elapsed since the last request,
   117  // starting when this is invoked.
   118  func (d *Drainer) Drain() {
   119  	// Note: until the first caller exits, the others
   120  	// will wait blocked as well.
   121  	ch := func() chan struct{} {
   122  		d.Lock()
   123  		defer d.Unlock()
   124  		if d.drainCh != nil {
   125  			return d.drainCh
   126  		}
   127  
   128  		if d.QuietPeriod <= 0 {
   129  			d.QuietPeriod = network.DefaultDrainTimeout
   130  		}
   131  
   132  		timer := newTimer(d.QuietPeriod)
   133  		drainCh := make(chan struct{})
   134  		resetCh := make(chan struct{})
   135  
   136  		go func() {
   137  			select {
   138  			case <-resetCh:
   139  			case <-timer.tickChan():
   140  			}
   141  			close(drainCh)
   142  		}()
   143  
   144  		d.drainCh = drainCh
   145  		d.resetCh = resetCh
   146  		d.timer = timer
   147  		return drainCh
   148  	}()
   149  
   150  	<-ch
   151  }
   152  
   153  // isHealthcheckRequest validates if the request has a user agent that is for healthcheck
   154  func (d *Drainer) isHealthCheckRequest(r *http.Request) bool {
   155  	if network.IsKubeletProbe(r) {
   156  		return true
   157  	}
   158  
   159  	for _, ua := range d.HealthCheckUAPrefixes {
   160  		if strings.HasPrefix(r.Header.Get(network.UserAgentKey), ua) {
   161  			return true
   162  		}
   163  	}
   164  
   165  	return false
   166  }
   167  
   168  // Reset interrupts Drain and clears the drainers internal state
   169  // Thus further calls to Drain will block and wait for the entire QuietPeriod
   170  func (d *Drainer) Reset() {
   171  	d.Lock()
   172  	defer d.Unlock()
   173  
   174  	if d.timer != nil {
   175  		d.timer.Stop()
   176  		d.timer = nil
   177  	}
   178  
   179  	if d.resetCh != nil {
   180  		close(d.resetCh)
   181  		d.resetCh = nil
   182  	}
   183  	if d.drainCh != nil {
   184  		d.drainCh = nil
   185  	}
   186  }
   187  
   188  func (d *Drainer) resetTimer() {
   189  	if func() bool {
   190  		d.RLock()
   191  		defer d.RUnlock()
   192  		return d.timer == nil
   193  	}() {
   194  		return
   195  	}
   196  
   197  	d.Lock()
   198  	defer d.Unlock()
   199  	if d.timer != nil && d.timer.Stop() {
   200  		d.timer.Reset(d.QuietPeriod)
   201  	}
   202  }
   203  
   204  // draining returns whether we are draining the handler.
   205  func (d *Drainer) draining() bool {
   206  	d.RLock()
   207  	defer d.RUnlock()
   208  	return d.timer != nil
   209  }
   210  
   211  // isKProbe returns true if the request is a knatvie probe.
   212  func isKProbe(r *http.Request) bool {
   213  	return r.Header.Get(network.ProbeHeaderName) == network.ProbeHeaderValue
   214  }
   215  
   216  // serveKProbe serve KProbe requests.
   217  func serveKProbe(w http.ResponseWriter, r *http.Request) {
   218  	hh := r.Header.Get(network.HashHeaderName)
   219  	if hh == "" {
   220  		http.Error(w,
   221  			fmt.Sprintf("a probe request must contain a non-empty %q header", network.HashHeaderName),
   222  			http.StatusBadRequest)
   223  		return
   224  	}
   225  	w.Header().Set(network.HashHeaderName, hh)
   226  	w.WriteHeader(http.StatusOK)
   227  }