145
vendor/k8s.io/client-go/rest/request.go
generated
vendored
145
vendor/k8s.io/client-go/rest/request.go
generated
vendored
@@ -45,7 +45,7 @@ import (
|
||||
restclientwatch "k8s.io/client-go/rest/watch"
|
||||
"k8s.io/client-go/tools/metrics"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
"k8s.io/klog"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -88,9 +88,12 @@ var noBackoff = &NoBackoff{}
|
||||
type Request struct {
|
||||
c *RESTClient
|
||||
|
||||
warningHandler WarningHandler
|
||||
|
||||
rateLimiter flowcontrol.RateLimiter
|
||||
backoff BackoffManager
|
||||
timeout time.Duration
|
||||
maxRetries int
|
||||
|
||||
// generic components accessible via method setters
|
||||
verb string
|
||||
@@ -134,11 +137,13 @@ func NewRequest(c *RESTClient) *Request {
|
||||
}
|
||||
|
||||
r := &Request{
|
||||
c: c,
|
||||
rateLimiter: c.rateLimiter,
|
||||
backoff: backoff,
|
||||
timeout: timeout,
|
||||
pathPrefix: pathPrefix,
|
||||
c: c,
|
||||
rateLimiter: c.rateLimiter,
|
||||
backoff: backoff,
|
||||
timeout: timeout,
|
||||
pathPrefix: pathPrefix,
|
||||
maxRetries: 10,
|
||||
warningHandler: c.warningHandler,
|
||||
}
|
||||
|
||||
switch {
|
||||
@@ -216,6 +221,13 @@ func (r *Request) BackOff(manager BackoffManager) *Request {
|
||||
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).
|
||||
func (r *Request) WarningHandler(handler WarningHandler) *Request {
|
||||
r.warningHandler = handler
|
||||
return r
|
||||
}
|
||||
|
||||
// Throttle receives a rate-limiter and sets or replaces an existing request limiter
|
||||
func (r *Request) Throttle(limiter flowcontrol.RateLimiter) *Request {
|
||||
r.rateLimiter = limiter
|
||||
@@ -230,7 +242,7 @@ func (r *Request) SubResource(subresources ...string) *Request {
|
||||
}
|
||||
subresource := path.Join(subresources...)
|
||||
if len(r.subresource) != 0 {
|
||||
r.err = fmt.Errorf("subresource already set to %q, cannot change to %q", r.resource, subresource)
|
||||
r.err = fmt.Errorf("subresource already set to %q, cannot change to %q", r.subresource, subresource)
|
||||
return r
|
||||
}
|
||||
for _, s := range subresources {
|
||||
@@ -391,6 +403,18 @@ func (r *Request) Timeout(d time.Duration) *Request {
|
||||
return r
|
||||
}
|
||||
|
||||
// MaxRetries makes the request use the given integer as a ceiling of retrying upon receiving
|
||||
// "Retry-After" headers and 429 status-code in the response. The default is 10 unless this
|
||||
// function is specifically called with a different value.
|
||||
// A zero maxRetries prevent it from doing retires and return an error immediately.
|
||||
func (r *Request) MaxRetries(maxRetries int) *Request {
|
||||
if maxRetries < 0 {
|
||||
maxRetries = 0
|
||||
}
|
||||
r.maxRetries = maxRetries
|
||||
return r
|
||||
}
|
||||
|
||||
// Body makes the request use obj as the body. Optional.
|
||||
// If obj is a string, try to read a file of that name.
|
||||
// If obj is a []byte, send it directly.
|
||||
@@ -487,13 +511,23 @@ func (r Request) finalURLTemplate() url.URL {
|
||||
}
|
||||
r.params = newParams
|
||||
url := r.URL()
|
||||
segments := strings.Split(r.URL().Path, "/")
|
||||
|
||||
segments := strings.Split(url.Path, "/")
|
||||
groupIndex := 0
|
||||
index := 0
|
||||
if r.URL() != nil && r.c.base != nil && strings.Contains(r.URL().Path, r.c.base.Path) {
|
||||
groupIndex += len(strings.Split(r.c.base.Path, "/"))
|
||||
trimmedBasePath := ""
|
||||
if url != nil && r.c.base != nil && strings.Contains(url.Path, r.c.base.Path) {
|
||||
p := strings.TrimPrefix(url.Path, r.c.base.Path)
|
||||
if !strings.HasPrefix(p, "/") {
|
||||
p = "/" + p
|
||||
}
|
||||
// store the base path that we have trimmed so we can append it
|
||||
// before returning the URL
|
||||
trimmedBasePath = r.c.base.Path
|
||||
segments = strings.Split(p, "/")
|
||||
groupIndex = 1
|
||||
}
|
||||
if groupIndex >= len(segments) {
|
||||
if len(segments) <= 2 {
|
||||
return *url
|
||||
}
|
||||
|
||||
@@ -539,11 +573,11 @@ func (r Request) finalURLTemplate() url.URL {
|
||||
segments[index+3] = "{name}"
|
||||
}
|
||||
}
|
||||
url.Path = path.Join(segments...)
|
||||
url.Path = path.Join(trimmedBasePath, path.Join(segments...))
|
||||
return *url
|
||||
}
|
||||
|
||||
func (r *Request) tryThrottle(ctx context.Context) error {
|
||||
func (r *Request) tryThrottleWithInfo(ctx context.Context, retryInfo string) error {
|
||||
if r.rateLimiter == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -553,19 +587,32 @@ func (r *Request) tryThrottle(ctx context.Context) error {
|
||||
err := r.rateLimiter.Wait(ctx)
|
||||
|
||||
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).Infof("Throttling request took %v, request: %s:%s", latency, r.verb, r.URL().String())
|
||||
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("Throttling request took %v, request: %s:%s", latency, r.verb, r.URL().String())
|
||||
globalThrottledLogger.Infof(message)
|
||||
}
|
||||
metrics.RateLimiterLatency.Observe(r.verb, r.finalURLTemplate(), latency)
|
||||
metrics.RateLimiterLatency.Observe(ctx, r.verb, r.finalURLTemplate(), latency)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Request) tryThrottle(ctx context.Context) error {
|
||||
return r.tryThrottleWithInfo(ctx, "")
|
||||
}
|
||||
|
||||
type throttleSettings struct {
|
||||
logLevel klog.Level
|
||||
minLogInterval time.Duration
|
||||
@@ -594,7 +641,7 @@ var globalThrottledLogger = &throttledLogger{
|
||||
|
||||
func (b *throttledLogger) attemptToLog() (klog.Level, bool) {
|
||||
for _, setting := range b.settings {
|
||||
if bool(klog.V(setting.logLevel)) {
|
||||
if bool(klog.V(setting.logLevel).Enabled()) {
|
||||
// Return early without write locking if possible.
|
||||
if func() bool {
|
||||
setting.lock.RLock()
|
||||
@@ -614,7 +661,7 @@ func (b *throttledLogger) attemptToLog() (klog.Level, bool) {
|
||||
return -1, false
|
||||
}
|
||||
|
||||
// Infof will write a log message at each logLevel specified by the reciever's throttleSettings
|
||||
// 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 {
|
||||
@@ -644,7 +691,7 @@ func (r *Request) Watch(ctx context.Context) (watch.Interface, error) {
|
||||
}
|
||||
r.backoff.Sleep(r.backoff.CalculateBackoff(r.URL()))
|
||||
resp, err := client.Do(req)
|
||||
updateURLMetrics(r, resp, err)
|
||||
updateURLMetrics(ctx, r, resp, err)
|
||||
if r.c.base != nil {
|
||||
if err != nil {
|
||||
r.backoff.UpdateBackoff(r.c.base, err, 0)
|
||||
@@ -678,6 +725,8 @@ func (r *Request) Watch(ctx context.Context) (watch.Interface, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handleWarnings(resp.Header, r.warningHandler)
|
||||
|
||||
frameReader := framer.NewFrameReader(resp.Body)
|
||||
watchEventDecoder := streaming.NewDecoder(frameReader, streamingSerializer)
|
||||
|
||||
@@ -691,7 +740,7 @@ func (r *Request) Watch(ctx context.Context) (watch.Interface, error) {
|
||||
|
||||
// updateURLMetrics is a convenience function for pushing metrics.
|
||||
// It also handles corner cases for incomplete/invalid request data.
|
||||
func updateURLMetrics(req *Request, resp *http.Response, err error) {
|
||||
func updateURLMetrics(ctx context.Context, req *Request, resp *http.Response, err error) {
|
||||
url := "none"
|
||||
if req.c.base != nil {
|
||||
url = req.c.base.Host
|
||||
@@ -700,10 +749,10 @@ func updateURLMetrics(req *Request, resp *http.Response, err error) {
|
||||
// Errors can be arbitrary strings. Unbound label cardinality is not suitable for a metric
|
||||
// system so we just report them as `<error>`.
|
||||
if err != nil {
|
||||
metrics.RequestResult.Increment("<error>", req.verb, url)
|
||||
metrics.RequestResult.Increment(ctx, "<error>", req.verb, url)
|
||||
} else {
|
||||
//Metrics for failure codes
|
||||
metrics.RequestResult.Increment(strconv.Itoa(resp.StatusCode), req.verb, url)
|
||||
metrics.RequestResult.Increment(ctx, strconv.Itoa(resp.StatusCode), req.verb, url)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,7 +785,7 @@ func (r *Request) Stream(ctx context.Context) (io.ReadCloser, error) {
|
||||
}
|
||||
r.backoff.Sleep(r.backoff.CalculateBackoff(r.URL()))
|
||||
resp, err := client.Do(req)
|
||||
updateURLMetrics(r, resp, err)
|
||||
updateURLMetrics(ctx, r, resp, err)
|
||||
if r.c.base != nil {
|
||||
if err != nil {
|
||||
r.backoff.UpdateBackoff(r.URL(), err, 0)
|
||||
@@ -750,6 +799,7 @@ func (r *Request) Stream(ctx context.Context) (io.ReadCloser, error) {
|
||||
|
||||
switch {
|
||||
case (resp.StatusCode >= 200) && (resp.StatusCode < 300):
|
||||
handleWarnings(resp.Header, r.warningHandler)
|
||||
return resp.Body, nil
|
||||
|
||||
default:
|
||||
@@ -800,7 +850,7 @@ func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Resp
|
||||
//Metrics for total request latency
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
metrics.RequestLatency.Observe(r.verb, r.finalURLTemplate(), time.Since(start))
|
||||
metrics.RequestLatency.Observe(ctx, r.verb, r.finalURLTemplate(), time.Since(start))
|
||||
}()
|
||||
|
||||
if r.err != nil {
|
||||
@@ -831,8 +881,8 @@ func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Resp
|
||||
}
|
||||
|
||||
// Right now we make about ten retry attempts if we get a Retry-After response.
|
||||
maxRetries := 10
|
||||
retries := 0
|
||||
var retryInfo string
|
||||
for {
|
||||
|
||||
url := r.URL().String()
|
||||
@@ -848,12 +898,13 @@ func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Resp
|
||||
// We are retrying the request that we already send to apiserver
|
||||
// at least once before.
|
||||
// This request should also be throttled with the client-internal rate limiter.
|
||||
if err := r.tryThrottle(ctx); err != nil {
|
||||
if err := r.tryThrottleWithInfo(ctx, retryInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
retryInfo = ""
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
updateURLMetrics(r, resp, err)
|
||||
updateURLMetrics(ctx, r, resp, err)
|
||||
if err != nil {
|
||||
r.backoff.UpdateBackoff(r.URL(), err, 0)
|
||||
} else {
|
||||
@@ -894,7 +945,8 @@ func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Resp
|
||||
}()
|
||||
|
||||
retries++
|
||||
if seconds, wait := checkWait(resp); wait && retries < maxRetries {
|
||||
if seconds, wait := checkWait(resp); wait && retries <= r.maxRetries {
|
||||
retryInfo = getRetryReason(retries, seconds, resp, err)
|
||||
if seeker, ok := r.body.(io.Seeker); ok && r.body != nil {
|
||||
_, err := seeker.Seek(0, 0)
|
||||
if err != nil {
|
||||
@@ -1007,6 +1059,7 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu
|
||||
body: body,
|
||||
contentType: contentType,
|
||||
statusCode: resp.StatusCode,
|
||||
warnings: handleWarnings(resp.Header, r.warningHandler),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1025,6 +1078,7 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu
|
||||
statusCode: resp.StatusCode,
|
||||
decoder: decoder,
|
||||
err: err,
|
||||
warnings: handleWarnings(resp.Header, r.warningHandler),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1033,6 +1087,7 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu
|
||||
contentType: contentType,
|
||||
statusCode: resp.StatusCode,
|
||||
decoder: decoder,
|
||||
warnings: handleWarnings(resp.Header, r.warningHandler),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1040,11 +1095,11 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu
|
||||
func truncateBody(body string) string {
|
||||
max := 0
|
||||
switch {
|
||||
case bool(klog.V(10)):
|
||||
case bool(klog.V(10).Enabled()):
|
||||
return body
|
||||
case bool(klog.V(9)):
|
||||
case bool(klog.V(9).Enabled()):
|
||||
max = 10240
|
||||
case bool(klog.V(8)):
|
||||
case bool(klog.V(8).Enabled()):
|
||||
max = 1024
|
||||
}
|
||||
|
||||
@@ -1059,7 +1114,7 @@ func truncateBody(body string) string {
|
||||
// allocating a new string for the body output unless necessary. Uses a simple heuristic to determine
|
||||
// whether the body is printable.
|
||||
func glogBody(prefix string, body []byte) {
|
||||
if klog.V(8) {
|
||||
if klog.V(8).Enabled() {
|
||||
if bytes.IndexFunc(body, func(r rune) bool {
|
||||
return r < 0x0a
|
||||
}) != -1 {
|
||||
@@ -1165,9 +1220,30 @@ func retryAfterSeconds(resp *http.Response) (int, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func getRetryReason(retries, seconds int, resp *http.Response, err error) string {
|
||||
// priority and fairness sets the UID of the FlowSchema associated with a request
|
||||
// in the following response Header.
|
||||
const responseHeaderMatchedFlowSchemaUID = "X-Kubernetes-PF-FlowSchema-UID"
|
||||
|
||||
message := fmt.Sprintf("retries: %d, retry-after: %ds", retries, seconds)
|
||||
|
||||
switch {
|
||||
case resp.StatusCode == http.StatusTooManyRequests:
|
||||
// it is server-side throttling from priority and fairness
|
||||
flowSchemaUID := resp.Header.Get(responseHeaderMatchedFlowSchemaUID)
|
||||
return fmt.Sprintf("%s - retry-reason: due to server-side throttling, FlowSchema UID: %q", message, flowSchemaUID)
|
||||
case err != nil:
|
||||
// it's a retriable error
|
||||
return fmt.Sprintf("%s - retry-reason: due to retriable error, error: %v", message, err)
|
||||
default:
|
||||
return fmt.Sprintf("%s - retry-reason: %d", message, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// Result contains the result of calling Request.Do().
|
||||
type Result struct {
|
||||
body []byte
|
||||
warnings []net.WarningHeader
|
||||
contentType string
|
||||
err error
|
||||
statusCode int
|
||||
@@ -1281,6 +1357,11 @@ func (r Result) Error() error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
// Warnings returns any warning headers received in the response
|
||||
func (r Result) Warnings() []net.WarningHeader {
|
||||
return r.warnings
|
||||
}
|
||||
|
||||
// NameMayNotBe specifies strings that cannot be used as names specified as path segments (like the REST API or etcd store)
|
||||
var NameMayNotBe = []string{".", ".."}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user