k8s.io/apiserver@v0.31.1/pkg/endpoints/filters/warning.go (about) 1 /* 2 Copyright 2020 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 filters 18 19 import ( 20 "fmt" 21 "net/http" 22 "sync" 23 "unicode/utf8" 24 25 "k8s.io/apimachinery/pkg/util/net" 26 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 27 "k8s.io/apiserver/pkg/warning" 28 ) 29 30 // WithWarningRecorder attaches a deduplicating k8s.io/apiserver/pkg/warning#WarningRecorder to the request context. 31 func WithWarningRecorder(handler http.Handler) http.Handler { 32 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 33 recorder := &recorder{writer: w} 34 req = req.WithContext(warning.WithWarningRecorder(req.Context(), recorder)) 35 handler.ServeHTTP(w, req) 36 }) 37 } 38 39 var ( 40 truncateAtTotalRunes = 4 * 1024 41 truncateItemRunes = 256 42 ) 43 44 type recordedWarning struct { 45 agent string 46 text string 47 } 48 49 type recorder struct { 50 // lock guards calls to AddWarning from multiple threads 51 lock sync.Mutex 52 53 // recorded tracks whether AddWarning was already called with a given text 54 recorded map[string]bool 55 56 // ordered tracks warnings added so they can be replayed and truncated if needed 57 ordered []recordedWarning 58 59 // written tracks how many runes of text have been added as warning headers 60 written int 61 62 // truncating tracks if we have already exceeded truncateAtTotalRunes and are now truncating warning messages as we add them 63 truncating bool 64 65 // writer is the response writer to add warning headers to 66 writer http.ResponseWriter 67 } 68 69 func (r *recorder) AddWarning(agent, text string) { 70 if len(text) == 0 { 71 return 72 } 73 74 r.lock.Lock() 75 defer r.lock.Unlock() 76 77 // if we've already exceeded our limit and are already truncating, return early 78 if r.written >= truncateAtTotalRunes && r.truncating { 79 return 80 } 81 82 // init if needed 83 if r.recorded == nil { 84 r.recorded = map[string]bool{} 85 } 86 87 // dedupe if already warned 88 if r.recorded[text] { 89 return 90 } 91 r.recorded[text] = true 92 r.ordered = append(r.ordered, recordedWarning{agent: agent, text: text}) 93 94 // truncate on a rune boundary, if needed 95 textRuneLength := utf8.RuneCountInString(text) 96 if r.truncating && textRuneLength > truncateItemRunes { 97 text = string([]rune(text)[:truncateItemRunes]) 98 textRuneLength = truncateItemRunes 99 } 100 101 // compute the header 102 header, err := net.NewWarningHeader(299, agent, text) 103 if err != nil { 104 return 105 } 106 107 // if this fits within our limit, or we're already truncating, write and return 108 if r.written+textRuneLength <= truncateAtTotalRunes || r.truncating { 109 r.written += textRuneLength 110 r.writer.Header().Add("Warning", header) 111 return 112 } 113 114 // otherwise, enable truncation, reset, and replay the existing items as truncated warnings 115 r.truncating = true 116 r.written = 0 117 r.writer.Header().Del("Warning") 118 utilruntime.HandleError(fmt.Errorf("exceeded max warning header size, truncating")) 119 for _, w := range r.ordered { 120 agent := w.agent 121 text := w.text 122 123 textRuneLength := utf8.RuneCountInString(text) 124 if textRuneLength > truncateItemRunes { 125 text = string([]rune(text)[:truncateItemRunes]) 126 textRuneLength = truncateItemRunes 127 } 128 if header, err := net.NewWarningHeader(299, agent, text); err == nil { 129 r.written += textRuneLength 130 r.writer.Header().Add("Warning", header) 131 } 132 } 133 }