rebase: bump k8s.io/kubernetes in the k8s-dependencies group

Bumps the k8s-dependencies group with 1 update: [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes).

Updates `k8s.io/kubernetes` from 1.32.3 to 1.33.0
- [Release notes](https://github.com/kubernetes/kubernetes/releases)
- [Commits](https://github.com/kubernetes/kubernetes/compare/v1.32.3...v1.33.0)

---
updated-dependencies:
- dependency-name: k8s.io/kubernetes
  dependency-version: 1.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: k8s-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
dependabot[bot]
2025-04-28 22:16:28 +00:00
committed by mergify[bot]
parent 4147d5d15a
commit 51895f8619
699 changed files with 51590 additions and 17096 deletions

10
vendor/k8s.io/client-go/rest/.mockery.yaml generated vendored Normal file
View File

@ -0,0 +1,10 @@
---
dir: .
filename: "mock_{{.InterfaceName | snakecase}}_test.go"
boilerplate-file: ../../../../../hack/boilerplate/boilerplate.generatego.txt
outpkg: rest
with-expecter: true
packages:
k8s.io/client-go/rest:
interfaces:
BackoffManager:

View File

@ -93,7 +93,7 @@ type RESTClient struct {
content requestClientContentConfigProvider
// creates BackoffManager that is passed to requests.
createBackoffMgr func() BackoffManager
createBackoffMgr func() BackoffManagerWithContext
// rateLimiter is shared among all requests created by this client unless specifically
// overridden.
@ -101,7 +101,7 @@ type RESTClient struct {
// warningHandler is shared among all requests created by this client.
// If not set, defaultWarningHandler is used.
warningHandler WarningHandler
warningHandler WarningHandlerWithContext
// Set specific behavior of the client. If not set http.DefaultClient will be used.
Client *http.Client
@ -178,7 +178,7 @@ func (c *RESTClient) GetRateLimiter() flowcontrol.RateLimiter {
// readExpBackoffConfig handles the internal logic of determining what the
// backoff policy is. By default if no information is available, NoBackoff.
// TODO Generalize this see #17727 .
func readExpBackoffConfig() BackoffManager {
func readExpBackoffConfig() BackoffManagerWithContext {
backoffBase := os.Getenv(envBackoffBase)
backoffDuration := os.Getenv(envBackoffDuration)

View File

@ -129,10 +129,23 @@ type Config struct {
RateLimiter flowcontrol.RateLimiter
// WarningHandler handles warnings in server responses.
// If not set, the default warning handler is used.
// See documentation for SetDefaultWarningHandler() for details.
// If this and WarningHandlerWithContext are not set, the
// default warning handler is used. If both are set,
// WarningHandlerWithContext is used.
//
// See documentation for [SetDefaultWarningHandler] for details.
//
//logcheck:context // WarningHandlerWithContext should be used instead of WarningHandler in code which supports contextual logging.
WarningHandler WarningHandler
// WarningHandlerWithContext handles warnings in server responses.
// If this and WarningHandler are not set, the
// default warning handler is used. If both are set,
// WarningHandlerWithContext is used.
//
// See documentation for [SetDefaultWarningHandler] for details.
WarningHandlerWithContext WarningHandlerWithContext
// The maximum length of time to wait before giving up on a server request. A value of zero means no timeout.
Timeout time.Duration
@ -381,12 +394,27 @@ func RESTClientForConfigAndClient(config *Config, httpClient *http.Client) (*RES
}
restClient, err := NewRESTClient(baseURL, versionedAPIPath, clientContent, rateLimiter, httpClient)
if err == nil && config.WarningHandler != nil {
restClient.warningHandler = config.WarningHandler
}
maybeSetWarningHandler(restClient, config.WarningHandler, config.WarningHandlerWithContext)
return restClient, err
}
// maybeSetWarningHandler sets the handlerWithContext if non-nil,
// otherwise the handler with a wrapper if non-nil,
// and does nothing if both are nil.
//
// May be called for a nil client.
func maybeSetWarningHandler(c *RESTClient, handler WarningHandler, handlerWithContext WarningHandlerWithContext) {
if c == nil {
return
}
switch {
case handlerWithContext != nil:
c.warningHandler = handlerWithContext
case handler != nil:
c.warningHandler = warningLoggerNopContext{l: handler}
}
}
// UnversionedRESTClientFor is the same as RESTClientFor, except that it allows
// the config.Version to be empty.
func UnversionedRESTClientFor(config *Config) (*RESTClient, error) {
@ -448,9 +476,7 @@ func UnversionedRESTClientForConfigAndClient(config *Config, httpClient *http.Cl
}
restClient, err := NewRESTClient(baseURL, versionedAPIPath, clientContent, rateLimiter, httpClient)
if err == nil && config.WarningHandler != nil {
restClient.warningHandler = config.WarningHandler
}
maybeSetWarningHandler(restClient, config.WarningHandler, config.WarningHandlerWithContext)
return restClient, err
}
@ -532,6 +558,7 @@ func InClusterConfig() (*Config, error) {
tlsClientConfig := TLSClientConfig{}
if _, err := certutil.NewPool(rootCAFile); err != nil {
//nolint:logcheck // The decision to log this instead of returning an error goes back to ~2016. It's part of the client-go API now, so not changing it just to support contextual logging.
klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
} else {
tlsClientConfig.CAFile = rootCAFile
@ -616,15 +643,16 @@ func AnonymousClientConfig(config *Config) *Config {
CAData: config.TLSClientConfig.CAData,
NextProtos: config.TLSClientConfig.NextProtos,
},
RateLimiter: config.RateLimiter,
WarningHandler: config.WarningHandler,
UserAgent: config.UserAgent,
DisableCompression: config.DisableCompression,
QPS: config.QPS,
Burst: config.Burst,
Timeout: config.Timeout,
Dial: config.Dial,
Proxy: config.Proxy,
RateLimiter: config.RateLimiter,
WarningHandler: config.WarningHandler,
WarningHandlerWithContext: config.WarningHandlerWithContext,
UserAgent: config.UserAgent,
DisableCompression: config.DisableCompression,
QPS: config.QPS,
Burst: config.Burst,
Timeout: config.Timeout,
Dial: config.Dial,
Proxy: config.Proxy,
}
}
@ -658,17 +686,18 @@ func CopyConfig(config *Config) *Config {
CAData: config.TLSClientConfig.CAData,
NextProtos: config.TLSClientConfig.NextProtos,
},
UserAgent: config.UserAgent,
DisableCompression: config.DisableCompression,
Transport: config.Transport,
WrapTransport: config.WrapTransport,
QPS: config.QPS,
Burst: config.Burst,
RateLimiter: config.RateLimiter,
WarningHandler: config.WarningHandler,
Timeout: config.Timeout,
Dial: config.Dial,
Proxy: config.Proxy,
UserAgent: config.UserAgent,
DisableCompression: config.DisableCompression,
Transport: config.Transport,
WrapTransport: config.WrapTransport,
QPS: config.QPS,
Burst: config.Burst,
RateLimiter: config.RateLimiter,
WarningHandler: config.WarningHandler,
WarningHandlerWithContext: config.WarningHandlerWithContext,
Timeout: config.Timeout,
Dial: config.Dial,
Proxy: config.Proxy,
}
if config.ExecProvider != nil && config.ExecProvider.Config != nil {
c.ExecProvider.Config = config.ExecProvider.Config.DeepCopyObject()

View File

@ -21,8 +21,6 @@ import (
"net/http"
"sync"
"k8s.io/klog/v2"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
@ -65,7 +63,10 @@ func RegisterAuthProviderPlugin(name string, plugin Factory) error {
if _, found := plugins[name]; found {
return fmt.Errorf("auth Provider Plugin %q was registered twice", name)
}
klog.V(4).Infof("Registered Auth Provider Plugin %q", name)
// RegisterAuthProviderPlugin gets called during the init phase before
// logging is initialized and therefore should not emit logs. If you
// need this message for debugging something, then uncomment it.
// klog.V(4).Infof("Registered Auth Provider Plugin %q", name)
plugins[name] = plugin
return nil
}

View File

@ -54,7 +54,7 @@ import (
"k8s.io/utils/clock"
)
var (
const (
// longThrottleLatency defines threshold for logging requests. All requests being
// throttled (via the provided rateLimiter) for more than longThrottleLatency will
// be logged.
@ -103,10 +103,10 @@ type Request struct {
contentConfig ClientContentConfig
contentTypeNotSet bool
warningHandler WarningHandler
warningHandler WarningHandlerWithContext
rateLimiter flowcontrol.RateLimiter
backoff BackoffManager
backoff BackoffManagerWithContext
timeout time.Duration
maxRetries int
@ -136,7 +136,7 @@ type Request struct {
// NewRequest creates a new request helper object for accessing runtime.Objects on a server.
func NewRequest(c *RESTClient) *Request {
var backoff BackoffManager
var backoff BackoffManagerWithContext
if c.createBackoffMgr != nil {
backoff = c.createBackoffMgr()
}
@ -259,20 +259,47 @@ func (r *Request) Resource(resource string) *Request {
}
// BackOff sets the request's backoff manager to the one specified,
// or defaults to the stub implementation if nil is provided
// or defaults to the stub implementation if nil is provided.
//
// Deprecated: BackoffManager.Sleep ignores the caller's context. Use BackOffWithContext and BackoffManagerWithContext instead.
func (r *Request) BackOff(manager BackoffManager) *Request {
if manager == nil {
r.backoff = &NoBackoff{}
return r
}
r.backoff = &backoffManagerNopContext{BackoffManager: manager}
return r
}
// BackOffWithContext sets the request's backoff manager to the one specified,
// or defaults to the stub implementation if nil is provided.
func (r *Request) BackOffWithContext(manager BackoffManagerWithContext) *Request {
if manager == nil {
r.backoff = &NoBackoff{}
return r
}
r.backoff = manager
return r
}
// WarningHandler sets the handler this client uses when warning headers are encountered.
// If set to nil, this client will use the default warning handler (see SetDefaultWarningHandler).
// If set to nil, this client will use the default warning handler (see [SetDefaultWarningHandler]).
//
//logcheck:context // WarningHandlerWithContext should be used instead of WarningHandler in code which supports contextual logging.
func (r *Request) WarningHandler(handler WarningHandler) *Request {
if handler == nil {
r.warningHandler = nil
return r
}
r.warningHandler = warningLoggerNopContext{l: handler}
return r
}
// WarningHandlerWithContext sets the handler this client uses when warning headers are encountered.
// If set to nil, this client will use the default warning handler (see [SetDefaultWarningHandlerWithContext]).
func (r *Request) WarningHandlerWithContext(handler WarningHandlerWithContext) *Request {
r.warningHandler = handler
return r
}
@ -649,21 +676,17 @@ func (r *Request) tryThrottleWithInfo(ctx context.Context, retryInfo string) err
}
latency := time.Since(now)
var message string
switch {
case len(retryInfo) > 0:
message = fmt.Sprintf("Waited for %v, %s - request: %s:%s", latency, retryInfo, r.verb, r.URL().String())
default:
message = fmt.Sprintf("Waited for %v due to client-side throttling, not priority and fairness, request: %s:%s", latency, r.verb, r.URL().String())
}
if latency > longThrottleLatency {
klog.V(3).Info(message)
}
if latency > extraLongThrottleLatency {
// If the rate limiter latency is very high, the log message should be printed at a higher log level,
// but we use a throttled logger to prevent spamming.
globalThrottledLogger.Infof("%s", message)
if retryInfo == "" {
retryInfo = "client-side throttling, not priority and fairness"
}
klog.FromContext(ctx).V(3).Info("Waited before sending request", "delay", latency, "reason", retryInfo, "verb", r.verb, "URL", r.URL())
if latency > extraLongThrottleLatency {
// If the rate limiter latency is very high, the log message should be printed at a higher log level,
// but we use a throttled logger to prevent spamming.
globalThrottledLogger.info(klog.FromContext(ctx), "Waited before sending request", "delay", latency, "reason", retryInfo, "verb", r.verb, "URL", r.URL())
}
}
metrics.RateLimiterLatency.Observe(ctx, r.verb, r.finalURLTemplate(), latency)
@ -675,7 +698,7 @@ func (r *Request) tryThrottle(ctx context.Context) error {
}
type throttleSettings struct {
logLevel klog.Level
logLevel int
minLogInterval time.Duration
lastLogTime time.Time
@ -700,9 +723,9 @@ var globalThrottledLogger = &throttledLogger{
},
}
func (b *throttledLogger) attemptToLog() (klog.Level, bool) {
func (b *throttledLogger) attemptToLog(logger klog.Logger) (int, bool) {
for _, setting := range b.settings {
if bool(klog.V(setting.logLevel).Enabled()) {
if bool(logger.V(setting.logLevel).Enabled()) {
// Return early without write locking if possible.
if func() bool {
setting.lock.RLock()
@ -724,9 +747,9 @@ func (b *throttledLogger) attemptToLog() (klog.Level, bool) {
// Infof will write a log message at each logLevel specified by the receiver's throttleSettings
// as long as it hasn't written a log message more recently than minLogInterval.
func (b *throttledLogger) Infof(message string, args ...interface{}) {
if logLevel, ok := b.attemptToLog(); ok {
klog.V(logLevel).Infof(message, args...)
func (b *throttledLogger) info(logger klog.Logger, message string, kv ...any) {
if logLevel, ok := b.attemptToLog(logger); ok {
logger.V(logLevel).Info(message, kv...)
}
}
@ -739,7 +762,7 @@ func (r *Request) Watch(ctx context.Context) (watch.Interface, error) {
func (r *Request) watchInternal(ctx context.Context) (watch.Interface, runtime.Decoder, error) {
if r.body == nil {
logBody(ctx, 2, "Request Body", r.bodyBytes)
logBody(klog.FromContext(ctx), 2, "Request Body", r.bodyBytes)
}
// We specifically don't want to rate limit watches, so we
@ -776,7 +799,7 @@ func (r *Request) watchInternal(ctx context.Context) (watch.Interface, runtime.D
resp, err := client.Do(req)
retry.After(ctx, r, resp, err)
if err == nil && resp.StatusCode == http.StatusOK {
return r.newStreamWatcher(resp)
return r.newStreamWatcher(ctx, resp)
}
done, transformErr := func() (bool, error) {
@ -898,7 +921,7 @@ func (r WatchListResult) Into(obj runtime.Object) error {
// to see what parameters are currently required.
func (r *Request) WatchList(ctx context.Context) WatchListResult {
if r.body == nil {
logBody(ctx, 2, "Request Body", r.bodyBytes)
logBody(klog.FromContext(ctx), 2, "Request Body", r.bodyBytes)
}
if !clientfeatures.FeatureGates().Enabled(clientfeatures.WatchListClient) {
@ -969,23 +992,24 @@ func (r *Request) handleWatchList(ctx context.Context, w watch.Interface, negoti
}
}
func (r *Request) newStreamWatcher(resp *http.Response) (watch.Interface, runtime.Decoder, error) {
func (r *Request) newStreamWatcher(ctx context.Context, resp *http.Response) (watch.Interface, runtime.Decoder, error) {
contentType := resp.Header.Get("Content-Type")
mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil {
klog.V(4).Infof("Unexpected content type from the server: %q: %v", contentType, err)
klog.FromContext(ctx).V(4).Info("Unexpected content type from the server", "contentType", contentType, "err", err)
}
objectDecoder, streamingSerializer, framer, err := r.contentConfig.Negotiator.StreamDecoder(mediaType, params)
if err != nil {
return nil, nil, err
}
handleWarnings(resp.Header, r.warningHandler)
handleWarnings(ctx, resp.Header, r.warningHandler)
frameReader := framer.NewFrameReader(resp.Body)
watchEventDecoder := streaming.NewDecoder(frameReader, streamingSerializer)
return watch.NewStreamWatcher(
return watch.NewStreamWatcherWithLogger(
klog.FromContext(ctx),
restclientwatch.NewDecoder(watchEventDecoder, objectDecoder),
// use 500 to indicate that the cause of the error is unknown - other error codes
// are more specific to HTTP interactions, and set a reason
@ -1031,7 +1055,7 @@ func sanitize(req *Request, resp *http.Response, err error) (string, string) {
// If we can, we return that as an error. Otherwise, we create an error that lists the http status and the content of the response.
func (r *Request) Stream(ctx context.Context) (io.ReadCloser, error) {
if r.body == nil {
logBody(ctx, 2, "Request Body", r.bodyBytes)
logBody(klog.FromContext(ctx), 2, "Request Body", r.bodyBytes)
}
if r.err != nil {
@ -1067,7 +1091,7 @@ func (r *Request) Stream(ctx context.Context) (io.ReadCloser, error) {
switch {
case (resp.StatusCode >= 200) && (resp.StatusCode < 300):
handleWarnings(resp.Header, r.warningHandler)
handleWarnings(ctx, resp.Header, r.warningHandler)
return resp.Body, nil
default:
@ -1175,7 +1199,7 @@ func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Resp
}()
if r.err != nil {
klog.V(4).Infof("Error in request: %v", r.err)
klog.FromContext(ctx).V(4).Info("Error in request", "err", r.err)
return r.err
}
@ -1267,8 +1291,9 @@ func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Resp
// - If the server responds with a status: *errors.StatusError or *errors.UnexpectedObjectError
// - http.Client.Do errors are returned directly.
func (r *Request) Do(ctx context.Context) Result {
logger := klog.FromContext(ctx)
if r.body == nil {
logBody(ctx, 2, "Request Body", r.bodyBytes)
logBody(logger, 2, "Request Body", r.bodyBytes)
}
var result Result
@ -1276,7 +1301,7 @@ func (r *Request) Do(ctx context.Context) Result {
result = r.transformResponse(ctx, resp, req)
})
if err != nil {
return Result{err: err}
return Result{err: err, logger: logger}
}
if result.err == nil || len(result.body) > 0 {
metrics.ResponseSize.Observe(ctx, r.verb, r.URL().Host, float64(len(result.body)))
@ -1286,14 +1311,15 @@ func (r *Request) Do(ctx context.Context) Result {
// DoRaw executes the request but does not process the response body.
func (r *Request) DoRaw(ctx context.Context) ([]byte, error) {
logger := klog.FromContext(ctx)
if r.body == nil {
logBody(ctx, 2, "Request Body", r.bodyBytes)
logBody(logger, 2, "Request Body", r.bodyBytes)
}
var result Result
err := r.request(ctx, func(req *http.Request, resp *http.Response) {
result.body, result.err = io.ReadAll(resp.Body)
logBody(ctx, 2, "Response Body", result.body)
logBody(logger, 2, "Response Body", result.body)
if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent {
result.err = r.transformUnstructuredResponseError(resp, req, result.body)
}
@ -1309,6 +1335,7 @@ func (r *Request) DoRaw(ctx context.Context) ([]byte, error) {
// transformResponse converts an API response into a structured API object
func (r *Request) transformResponse(ctx context.Context, resp *http.Response, req *http.Request) Result {
logger := klog.FromContext(ctx)
var body []byte
if resp.Body != nil {
data, err := io.ReadAll(resp.Body)
@ -1323,22 +1350,24 @@ func (r *Request) transformResponse(ctx context.Context, resp *http.Response, re
// 2. Apiserver sends back the headers and then part of the body
// 3. Apiserver closes connection.
// 4. client-go should catch this and return an error.
klog.V(2).Infof("Stream error %#v when reading response body, may be caused by closed connection.", err)
logger.V(2).Info("Stream error when reading response body, may be caused by closed connection", "err", err)
streamErr := fmt.Errorf("stream error when reading response body, may be caused by closed connection. Please retry. Original error: %w", err)
return Result{
err: streamErr,
err: streamErr,
logger: logger,
}
default:
klog.Errorf("Unexpected error when reading response body: %v", err)
logger.Error(err, "Unexpected error when reading response body")
unexpectedErr := fmt.Errorf("unexpected error when reading response body. Please retry. Original error: %w", err)
return Result{
err: unexpectedErr,
err: unexpectedErr,
logger: logger,
}
}
}
// Call depth is tricky. This one is okay for Do and DoRaw.
logBody(ctx, 7, "Response Body", body)
logBody(logger, 7, "Response Body", body)
// verify the content type is accurate
var decoder runtime.Decoder
@ -1350,7 +1379,7 @@ func (r *Request) transformResponse(ctx context.Context, resp *http.Response, re
var err error
mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil {
return Result{err: errors.NewInternalError(err)}
return Result{err: errors.NewInternalError(err), logger: logger}
}
decoder, err = r.contentConfig.Negotiator.Decoder(mediaType, params)
if err != nil {
@ -1359,13 +1388,14 @@ func (r *Request) transformResponse(ctx context.Context, resp *http.Response, re
case resp.StatusCode == http.StatusSwitchingProtocols:
// no-op, we've been upgraded
case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent:
return Result{err: r.transformUnstructuredResponseError(resp, req, body)}
return Result{err: r.transformUnstructuredResponseError(resp, req, body), logger: logger}
}
return Result{
body: body,
contentType: contentType,
statusCode: resp.StatusCode,
warnings: handleWarnings(resp.Header, r.warningHandler),
warnings: handleWarnings(ctx, resp.Header, r.warningHandler),
logger: logger,
}
}
}
@ -1384,7 +1414,8 @@ func (r *Request) transformResponse(ctx context.Context, resp *http.Response, re
statusCode: resp.StatusCode,
decoder: decoder,
err: err,
warnings: handleWarnings(resp.Header, r.warningHandler),
warnings: handleWarnings(ctx, resp.Header, r.warningHandler),
logger: logger,
}
}
@ -1393,7 +1424,8 @@ func (r *Request) transformResponse(ctx context.Context, resp *http.Response, re
contentType: contentType,
statusCode: resp.StatusCode,
decoder: decoder,
warnings: handleWarnings(resp.Header, r.warningHandler),
warnings: handleWarnings(ctx, resp.Header, r.warningHandler),
logger: logger,
}
}
@ -1421,8 +1453,7 @@ func truncateBody(logger klog.Logger, body string) string {
// whether the body is printable.
//
// It needs to be called by all functions which send or receive the data.
func logBody(ctx context.Context, callDepth int, prefix string, body []byte) {
logger := klog.FromContext(ctx)
func logBody(logger klog.Logger, callDepth int, prefix string, body []byte) {
if loggerV := logger.V(8); loggerV.Enabled() {
loggerV := loggerV.WithCallDepth(callDepth)
if bytes.IndexFunc(body, func(r rune) bool {
@ -1524,6 +1555,7 @@ type Result struct {
contentType string
err error
statusCode int
logger klog.Logger
decoder runtime.Decoder
}
@ -1629,7 +1661,7 @@ func (r Result) Error() error {
// to be backwards compatible with old servers that do not return a version, default to "v1"
out, _, err := r.decoder.Decode(r.body, &schema.GroupVersionKind{Version: "v1"}, nil)
if err != nil {
klog.V(5).Infof("body was not decodable (unable to check for Status): %v", err)
r.logger.V(5).Info("Body was not decodable (unable to check for Status)", "err", err)
return r.err
}
switch t := out.(type) {

View File

@ -17,6 +17,8 @@ limitations under the License.
package rest
import (
"context"
"fmt"
"net/url"
"time"
@ -32,12 +34,24 @@ import (
var serverIsOverloadedSet = sets.NewInt(429)
var maxResponseCode = 499
//go:generate mockery
// Deprecated: BackoffManager.Sleep ignores the caller's context. Use BackoffManagerWithContext instead.
type BackoffManager interface {
UpdateBackoff(actualUrl *url.URL, err error, responseCode int)
CalculateBackoff(actualUrl *url.URL) time.Duration
UpdateBackoff(actualURL *url.URL, err error, responseCode int)
CalculateBackoff(actualURL *url.URL) time.Duration
Sleep(d time.Duration)
}
type BackoffManagerWithContext interface {
UpdateBackoffWithContext(ctx context.Context, actualURL *url.URL, err error, responseCode int)
CalculateBackoffWithContext(ctx context.Context, actualURL *url.URL) time.Duration
SleepWithContext(ctx context.Context, d time.Duration)
}
var _ BackoffManager = &URLBackoff{}
var _ BackoffManagerWithContext = &URLBackoff{}
// URLBackoff struct implements the semantics on top of Backoff which
// we need for URL specific exponential backoff.
type URLBackoff struct {
@ -49,11 +63,19 @@ type URLBackoff struct {
type NoBackoff struct {
}
func (n *NoBackoff) UpdateBackoff(actualUrl *url.URL, err error, responseCode int) {
func (n *NoBackoff) UpdateBackoff(actualURL *url.URL, err error, responseCode int) {
// do nothing.
}
func (n *NoBackoff) CalculateBackoff(actualUrl *url.URL) time.Duration {
func (n *NoBackoff) UpdateBackoffWithContext(ctx context.Context, actualURL *url.URL, err error, responseCode int) {
// do nothing.
}
func (n *NoBackoff) CalculateBackoff(actualURL *url.URL) time.Duration {
return 0 * time.Second
}
func (n *NoBackoff) CalculateBackoffWithContext(ctx context.Context, actualURL *url.URL) time.Duration {
return 0 * time.Second
}
@ -61,10 +83,21 @@ func (n *NoBackoff) Sleep(d time.Duration) {
time.Sleep(d)
}
func (n *NoBackoff) SleepWithContext(ctx context.Context, d time.Duration) {
if d == 0 {
return
}
t := time.NewTimer(d)
defer t.Stop()
select {
case <-ctx.Done():
case <-t.C:
}
}
// Disable makes the backoff trivial, i.e., sets it to zero. This might be used
// by tests which want to run 1000s of mock requests without slowing down.
func (b *URLBackoff) Disable() {
klog.V(4).Infof("Disabling backoff strategy")
b.Backoff = flowcontrol.NewBackOff(0*time.Second, 0*time.Second)
}
@ -76,32 +109,74 @@ func (b *URLBackoff) baseUrlKey(rawurl *url.URL) string {
// in the future.
host, err := url.Parse(rawurl.String())
if err != nil {
klog.V(4).Infof("Error extracting url: %v", rawurl)
panic("bad url!")
panic(fmt.Sprintf("Error parsing bad URL %q: %v", rawurl, err))
}
return host.Host
}
// UpdateBackoff updates backoff metadata
func (b *URLBackoff) UpdateBackoff(actualUrl *url.URL, err error, responseCode int) {
func (b *URLBackoff) UpdateBackoff(actualURL *url.URL, err error, responseCode int) {
b.UpdateBackoffWithContext(context.Background(), actualURL, err, responseCode)
}
// UpdateBackoffWithContext updates backoff metadata
func (b *URLBackoff) UpdateBackoffWithContext(ctx context.Context, actualURL *url.URL, err error, responseCode int) {
// range for retry counts that we store is [0,13]
if responseCode > maxResponseCode || serverIsOverloadedSet.Has(responseCode) {
b.Backoff.Next(b.baseUrlKey(actualUrl), b.Backoff.Clock.Now())
b.Backoff.Next(b.baseUrlKey(actualURL), b.Backoff.Clock.Now())
return
} else if responseCode >= 300 || err != nil {
klog.V(4).Infof("Client is returning errors: code %v, error %v", responseCode, err)
klog.FromContext(ctx).V(4).Info("Client is returning errors", "code", responseCode, "err", err)
}
//If we got this far, there is no backoff required for this URL anymore.
b.Backoff.Reset(b.baseUrlKey(actualUrl))
b.Backoff.Reset(b.baseUrlKey(actualURL))
}
// CalculateBackoff takes a url and back's off exponentially,
// based on its knowledge of existing failures.
func (b *URLBackoff) CalculateBackoff(actualUrl *url.URL) time.Duration {
return b.Backoff.Get(b.baseUrlKey(actualUrl))
func (b *URLBackoff) CalculateBackoff(actualURL *url.URL) time.Duration {
return b.Backoff.Get(b.baseUrlKey(actualURL))
}
// CalculateBackoffWithContext takes a url and back's off exponentially,
// based on its knowledge of existing failures.
func (b *URLBackoff) CalculateBackoffWithContext(ctx context.Context, actualURL *url.URL) time.Duration {
return b.Backoff.Get(b.baseUrlKey(actualURL))
}
func (b *URLBackoff) Sleep(d time.Duration) {
b.Backoff.Clock.Sleep(d)
}
func (b *URLBackoff) SleepWithContext(ctx context.Context, d time.Duration) {
if d == 0 {
return
}
t := b.Backoff.Clock.NewTimer(d)
defer t.Stop()
select {
case <-ctx.Done():
case <-t.C():
}
}
// backoffManagerNopContext wraps a BackoffManager and adds the *WithContext methods.
type backoffManagerNopContext struct {
BackoffManager
}
var _ BackoffManager = &backoffManagerNopContext{}
var _ BackoffManagerWithContext = &backoffManagerNopContext{}
func (b *backoffManagerNopContext) UpdateBackoffWithContext(ctx context.Context, actualURL *url.URL, err error, responseCode int) {
b.UpdateBackoff(actualURL, err, responseCode)
}
func (b *backoffManagerNopContext) CalculateBackoffWithContext(ctx context.Context, actualURL *url.URL) time.Duration {
return b.CalculateBackoff(actualURL)
}
func (b *backoffManagerNopContext) SleepWithContext(ctx context.Context, d time.Duration) {
b.Sleep(d)
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package rest
import (
"context"
"fmt"
"io"
"net/http"
@ -33,8 +34,15 @@ type WarningHandler interface {
HandleWarningHeader(code int, agent string, text string)
}
// WarningHandlerWithContext is an interface for handling warning headers with
// support for contextual logging.
type WarningHandlerWithContext interface {
// HandleWarningHeaderWithContext is called with the warn code, agent, and text when a warning header is countered.
HandleWarningHeaderWithContext(ctx context.Context, code int, agent string, text string)
}
var (
defaultWarningHandler WarningHandler = WarningLogger{}
defaultWarningHandler WarningHandlerWithContext = WarningLogger{}
defaultWarningHandlerLock sync.RWMutex
)
@ -43,33 +51,68 @@ var (
// - NoWarnings suppresses warnings.
// - WarningLogger logs warnings.
// - NewWarningWriter() outputs warnings to the provided writer.
//
// logcheck:context // SetDefaultWarningHandlerWithContext should be used instead of SetDefaultWarningHandler in code which supports contextual logging.
func SetDefaultWarningHandler(l WarningHandler) {
if l == nil {
SetDefaultWarningHandlerWithContext(nil)
return
}
SetDefaultWarningHandlerWithContext(warningLoggerNopContext{l: l})
}
// SetDefaultWarningHandlerWithContext is a variant of [SetDefaultWarningHandler] which supports contextual logging.
func SetDefaultWarningHandlerWithContext(l WarningHandlerWithContext) {
defaultWarningHandlerLock.Lock()
defer defaultWarningHandlerLock.Unlock()
defaultWarningHandler = l
}
func getDefaultWarningHandler() WarningHandler {
func getDefaultWarningHandler() WarningHandlerWithContext {
defaultWarningHandlerLock.RLock()
defer defaultWarningHandlerLock.RUnlock()
l := defaultWarningHandler
return l
}
// NoWarnings is an implementation of WarningHandler that suppresses warnings.
type warningLoggerNopContext struct {
l WarningHandler
}
func (w warningLoggerNopContext) HandleWarningHeaderWithContext(_ context.Context, code int, agent string, message string) {
w.l.HandleWarningHeader(code, agent, message)
}
// NoWarnings is an implementation of [WarningHandler] and [WarningHandlerWithContext] that suppresses warnings.
type NoWarnings struct{}
func (NoWarnings) HandleWarningHeader(code int, agent string, message string) {}
func (NoWarnings) HandleWarningHeaderWithContext(ctx context.Context, code int, agent string, message string) {
}
// WarningLogger is an implementation of WarningHandler that logs code 299 warnings
var _ WarningHandler = NoWarnings{}
var _ WarningHandlerWithContext = NoWarnings{}
// WarningLogger is an implementation of [WarningHandler] and [WarningHandlerWithContext] that logs code 299 warnings
type WarningLogger struct{}
func (WarningLogger) HandleWarningHeader(code int, agent string, message string) {
if code != 299 || len(message) == 0 {
return
}
klog.Warning(message)
klog.Background().Info("Warning: " + message)
}
func (WarningLogger) HandleWarningHeaderWithContext(ctx context.Context, code int, agent string, message string) {
if code != 299 || len(message) == 0 {
return
}
klog.FromContext(ctx).Info("Warning: " + message)
}
var _ WarningHandler = WarningLogger{}
var _ WarningHandlerWithContext = WarningLogger{}
type warningWriter struct {
// out is the writer to output warnings to
out io.Writer
@ -134,14 +177,14 @@ func (w *warningWriter) WarningCount() int {
return w.writtenCount
}
func handleWarnings(headers http.Header, handler WarningHandler) []net.WarningHeader {
func handleWarnings(ctx context.Context, headers http.Header, handler WarningHandlerWithContext) []net.WarningHeader {
if handler == nil {
handler = getDefaultWarningHandler()
}
warnings, _ := net.ParseWarningHeaders(headers["Warning"])
for _, warning := range warnings {
handler.HandleWarningHeader(warning.Code, warning.Agent, warning.Text)
handler.HandleWarningHeaderWithContext(ctx, warning.Code, warning.Agent, warning.Text)
}
return warnings
}

View File

@ -209,18 +209,18 @@ func (r *withRetry) Before(ctx context.Context, request *Request) error {
// we do a backoff sleep before the first attempt is made,
// (preserving current behavior).
if request.backoff != nil {
request.backoff.Sleep(request.backoff.CalculateBackoff(url))
request.backoff.SleepWithContext(ctx, request.backoff.CalculateBackoffWithContext(ctx, url))
}
return nil
}
// if we are here, we have made attempt(s) at least once before.
if request.backoff != nil {
delay := request.backoff.CalculateBackoff(url)
delay := request.backoff.CalculateBackoffWithContext(ctx, url)
if r.retryAfter.Wait > delay {
delay = r.retryAfter.Wait
}
request.backoff.Sleep(delay)
request.backoff.SleepWithContext(ctx, delay)
}
// We are retrying the request that we already send to
@ -231,7 +231,7 @@ func (r *withRetry) Before(ctx context.Context, request *Request) error {
return err
}
klog.V(4).Infof("Got a Retry-After %s response for attempt %d to %v", r.retryAfter.Wait, r.retryAfter.Attempt, request.URL().String())
klog.FromContext(ctx).V(4).Info("Got a Retry-After response", "delay", r.retryAfter.Wait, "attempt", r.retryAfter.Attempt, "url", request.URL())
return nil
}
@ -258,9 +258,9 @@ func (r *withRetry) After(ctx context.Context, request *Request, resp *http.Resp
if request.c.base != nil {
if err != nil {
request.backoff.UpdateBackoff(request.URL(), err, 0)
request.backoff.UpdateBackoffWithContext(ctx, request.URL(), err, 0)
} else {
request.backoff.UpdateBackoff(request.URL(), err, resp.StatusCode)
request.backoff.UpdateBackoffWithContext(ctx, request.URL(), err, resp.StatusCode)
}
}
}