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 }