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  }