k8s.io/apiserver@v0.31.1/pkg/endpoints/handlers/watch.go (about) 1 /* 2 Copyright 2014 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 handlers 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "net/http" 24 "time" 25 26 "golang.org/x/net/websocket" 27 28 "k8s.io/apimachinery/pkg/api/errors" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/util/httpstream/wsstream" 32 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 33 "k8s.io/apimachinery/pkg/watch" 34 "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" 35 "k8s.io/apiserver/pkg/endpoints/metrics" 36 apirequest "k8s.io/apiserver/pkg/endpoints/request" 37 "k8s.io/apiserver/pkg/features" 38 "k8s.io/apiserver/pkg/storage" 39 utilfeature "k8s.io/apiserver/pkg/util/feature" 40 ) 41 42 // nothing will ever be sent down this channel 43 var neverExitWatch <-chan time.Time = make(chan time.Time) 44 45 // timeoutFactory abstracts watch timeout logic for testing 46 type TimeoutFactory interface { 47 TimeoutCh() (<-chan time.Time, func() bool) 48 } 49 50 // realTimeoutFactory implements timeoutFactory 51 type realTimeoutFactory struct { 52 timeout time.Duration 53 } 54 55 // TimeoutCh returns a channel which will receive something when the watch times out, 56 // and a cleanup function to call when this happens. 57 func (w *realTimeoutFactory) TimeoutCh() (<-chan time.Time, func() bool) { 58 if w.timeout == 0 { 59 return neverExitWatch, func() bool { return false } 60 } 61 t := time.NewTimer(w.timeout) 62 return t.C, t.Stop 63 } 64 65 // serveWatchHandler returns a handle to serve a watch response. 66 // TODO: the functionality in this method and in WatchServer.Serve is not cleanly decoupled. 67 func serveWatchHandler(watcher watch.Interface, scope *RequestScope, mediaTypeOptions negotiation.MediaTypeOptions, req *http.Request, w http.ResponseWriter, timeout time.Duration, metricsScope string) (http.Handler, error) { 68 options, err := optionsForTransform(mediaTypeOptions, req) 69 if err != nil { 70 return nil, err 71 } 72 73 // negotiate for the stream serializer from the scope's serializer 74 serializer, err := negotiation.NegotiateOutputMediaTypeStream(req, scope.Serializer, scope) 75 if err != nil { 76 return nil, err 77 } 78 framer := serializer.StreamSerializer.Framer 79 streamSerializer := serializer.StreamSerializer.Serializer 80 encoder := scope.Serializer.EncoderForVersion(streamSerializer, scope.Kind.GroupVersion()) 81 useTextFraming := serializer.EncodesAsText 82 if framer == nil { 83 return nil, fmt.Errorf("no framer defined for %q available for embedded encoding", serializer.MediaType) 84 } 85 // TODO: next step, get back mediaTypeOptions from negotiate and return the exact value here 86 mediaType := serializer.MediaType 87 if mediaType != runtime.ContentTypeJSON { 88 mediaType += ";stream=watch" 89 } 90 91 ctx := req.Context() 92 93 // locate the appropriate embedded encoder based on the transform 94 var embeddedEncoder runtime.Encoder 95 contentKind, contentSerializer, transform := targetEncodingForTransform(scope, mediaTypeOptions, req) 96 if transform { 97 info, ok := runtime.SerializerInfoForMediaType(contentSerializer.SupportedMediaTypes(), serializer.MediaType) 98 if !ok { 99 return nil, fmt.Errorf("no encoder for %q exists in the requested target %#v", serializer.MediaType, contentSerializer) 100 } 101 embeddedEncoder = contentSerializer.EncoderForVersion(info.Serializer, contentKind.GroupVersion()) 102 } else { 103 embeddedEncoder = scope.Serializer.EncoderForVersion(serializer.Serializer, contentKind.GroupVersion()) 104 } 105 106 var memoryAllocator runtime.MemoryAllocator 107 108 if encoderWithAllocator, supportsAllocator := embeddedEncoder.(runtime.EncoderWithAllocator); supportsAllocator { 109 // don't put the allocator inside the embeddedEncodeFn as that would allocate memory on every call. 110 // instead, we allocate the buffer for the entire watch session and release it when we close the connection. 111 memoryAllocator = runtime.AllocatorPool.Get().(*runtime.Allocator) 112 embeddedEncoder = runtime.NewEncoderWithAllocator(encoderWithAllocator, memoryAllocator) 113 } 114 var tableOptions *metav1.TableOptions 115 if options != nil { 116 if passedOptions, ok := options.(*metav1.TableOptions); ok { 117 tableOptions = passedOptions 118 } else { 119 return nil, fmt.Errorf("unexpected options type: %T", options) 120 } 121 } 122 embeddedEncoder = newWatchEmbeddedEncoder(ctx, embeddedEncoder, mediaTypeOptions.Convert, tableOptions, scope) 123 124 if encoderWithAllocator, supportsAllocator := encoder.(runtime.EncoderWithAllocator); supportsAllocator { 125 if memoryAllocator == nil { 126 // don't put the allocator inside the embeddedEncodeFn as that would allocate memory on every call. 127 // instead, we allocate the buffer for the entire watch session and release it when we close the connection. 128 memoryAllocator = runtime.AllocatorPool.Get().(*runtime.Allocator) 129 } 130 encoder = runtime.NewEncoderWithAllocator(encoderWithAllocator, memoryAllocator) 131 } 132 133 var serverShuttingDownCh <-chan struct{} 134 if signals := apirequest.ServerShutdownSignalFrom(req.Context()); signals != nil { 135 serverShuttingDownCh = signals.ShuttingDown() 136 } 137 138 server := &WatchServer{ 139 Watching: watcher, 140 Scope: scope, 141 142 UseTextFraming: useTextFraming, 143 MediaType: mediaType, 144 Framer: framer, 145 Encoder: encoder, 146 EmbeddedEncoder: embeddedEncoder, 147 148 MemoryAllocator: memoryAllocator, 149 TimeoutFactory: &realTimeoutFactory{timeout}, 150 ServerShuttingDownCh: serverShuttingDownCh, 151 152 metricsScope: metricsScope, 153 } 154 155 if wsstream.IsWebSocketRequest(req) { 156 w.Header().Set("Content-Type", server.MediaType) 157 return websocket.Handler(server.HandleWS), nil 158 } 159 return http.HandlerFunc(server.HandleHTTP), nil 160 } 161 162 // WatchServer serves a watch.Interface over a websocket or vanilla HTTP. 163 type WatchServer struct { 164 Watching watch.Interface 165 Scope *RequestScope 166 167 // true if websocket messages should use text framing (as opposed to binary framing) 168 UseTextFraming bool 169 // the media type this watch is being served with 170 MediaType string 171 // used to frame the watch stream 172 Framer runtime.Framer 173 // used to encode the watch stream event itself 174 Encoder runtime.Encoder 175 // used to encode the nested object in the watch stream 176 EmbeddedEncoder runtime.Encoder 177 178 MemoryAllocator runtime.MemoryAllocator 179 TimeoutFactory TimeoutFactory 180 ServerShuttingDownCh <-chan struct{} 181 182 metricsScope string 183 } 184 185 // HandleHTTP serves a series of encoded events via HTTP with Transfer-Encoding: chunked. 186 // or over a websocket connection. 187 func (s *WatchServer) HandleHTTP(w http.ResponseWriter, req *http.Request) { 188 defer func() { 189 if s.MemoryAllocator != nil { 190 runtime.AllocatorPool.Put(s.MemoryAllocator) 191 } 192 }() 193 194 flusher, ok := w.(http.Flusher) 195 if !ok { 196 err := fmt.Errorf("unable to start watch - can't get http.Flusher: %#v", w) 197 utilruntime.HandleError(err) 198 s.Scope.err(errors.NewInternalError(err), w, req) 199 return 200 } 201 202 framer := s.Framer.NewFrameWriter(w) 203 if framer == nil { 204 // programmer error 205 err := fmt.Errorf("no stream framing support is available for media type %q", s.MediaType) 206 utilruntime.HandleError(err) 207 s.Scope.err(errors.NewBadRequest(err.Error()), w, req) 208 return 209 } 210 211 // ensure the connection times out 212 timeoutCh, cleanup := s.TimeoutFactory.TimeoutCh() 213 defer cleanup() 214 215 // begin the stream 216 w.Header().Set("Content-Type", s.MediaType) 217 w.Header().Set("Transfer-Encoding", "chunked") 218 w.WriteHeader(http.StatusOK) 219 flusher.Flush() 220 221 kind := s.Scope.Kind 222 watchEncoder := newWatchEncoder(req.Context(), kind, s.EmbeddedEncoder, s.Encoder, framer) 223 ch := s.Watching.ResultChan() 224 done := req.Context().Done() 225 226 for { 227 select { 228 case <-s.ServerShuttingDownCh: 229 // the server has signaled that it is shutting down (not accepting 230 // any new request), all active watch request(s) should return 231 // immediately here. The WithWatchTerminationDuringShutdown server 232 // filter will ensure that the response to the client is rate 233 // limited in order to avoid any thundering herd issue when the 234 // client(s) try to reestablish the WATCH on the other 235 // available apiserver instance(s). 236 return 237 case <-done: 238 return 239 case <-timeoutCh: 240 return 241 case event, ok := <-ch: 242 if !ok { 243 // End of results. 244 return 245 } 246 metrics.WatchEvents.WithContext(req.Context()).WithLabelValues(kind.Group, kind.Version, kind.Kind).Inc() 247 isWatchListLatencyRecordingRequired := shouldRecordWatchListLatency(event) 248 249 if err := watchEncoder.Encode(event); err != nil { 250 utilruntime.HandleError(err) 251 // client disconnect. 252 return 253 } 254 255 if len(ch) == 0 { 256 flusher.Flush() 257 } 258 if isWatchListLatencyRecordingRequired { 259 metrics.RecordWatchListLatency(req.Context(), s.Scope.Resource, s.metricsScope) 260 } 261 } 262 } 263 } 264 265 // HandleWS serves a series of encoded events over a websocket connection. 266 func (s *WatchServer) HandleWS(ws *websocket.Conn) { 267 defer func() { 268 if s.MemoryAllocator != nil { 269 runtime.AllocatorPool.Put(s.MemoryAllocator) 270 } 271 }() 272 273 defer ws.Close() 274 done := make(chan struct{}) 275 // ensure the connection times out 276 timeoutCh, cleanup := s.TimeoutFactory.TimeoutCh() 277 defer cleanup() 278 279 go func() { 280 defer utilruntime.HandleCrash() 281 // This blocks until the connection is closed. 282 // Client should not send anything. 283 wsstream.IgnoreReceives(ws, 0) 284 // Once the client closes, we should also close 285 close(done) 286 }() 287 288 framer := newWebsocketFramer(ws, s.UseTextFraming) 289 290 kind := s.Scope.Kind 291 watchEncoder := newWatchEncoder(context.TODO(), kind, s.EmbeddedEncoder, s.Encoder, framer) 292 ch := s.Watching.ResultChan() 293 294 for { 295 select { 296 case <-done: 297 return 298 case <-timeoutCh: 299 return 300 case event, ok := <-ch: 301 if !ok { 302 // End of results. 303 return 304 } 305 306 if err := watchEncoder.Encode(event); err != nil { 307 utilruntime.HandleError(err) 308 // client disconnect. 309 return 310 } 311 } 312 } 313 } 314 315 type websocketFramer struct { 316 ws *websocket.Conn 317 useTextFraming bool 318 } 319 320 func newWebsocketFramer(ws *websocket.Conn, useTextFraming bool) io.Writer { 321 return &websocketFramer{ 322 ws: ws, 323 useTextFraming: useTextFraming, 324 } 325 } 326 327 func (w *websocketFramer) Write(p []byte) (int, error) { 328 if w.useTextFraming { 329 // bytes.Buffer::String() has a special handling of nil value, but given 330 // we're writing serialized watch events, this will never happen here. 331 if err := websocket.Message.Send(w.ws, string(p)); err != nil { 332 return 0, err 333 } 334 return len(p), nil 335 } 336 if err := websocket.Message.Send(w.ws, p); err != nil { 337 return 0, err 338 } 339 return len(p), nil 340 } 341 342 var _ io.Writer = &websocketFramer{} 343 344 func shouldRecordWatchListLatency(event watch.Event) bool { 345 if event.Type != watch.Bookmark || !utilfeature.DefaultFeatureGate.Enabled(features.WatchList) { 346 return false 347 } 348 // as of today the initial-events-end annotation is added only to a single event 349 // by the watch cache and only when certain conditions are met 350 // 351 // for more please read https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/3157-watch-list 352 hasAnnotation, err := storage.HasInitialEventsEndBookmarkAnnotation(event.Object) 353 if err != nil { 354 utilruntime.HandleError(fmt.Errorf("unable to determine if the obj has the required annotation for measuring watchlist latency, obj %T: %v", event.Object, err)) 355 return false 356 } 357 return hasAnnotation 358 }