Update k/k to 1.23.0

Signed-off-by: Aditi Sharma <adi.sky17@gmail.com>
This commit is contained in:
Aditi Sharma
2022-01-06 11:17:48 +05:30
parent 1b8af3752d
commit e43d4206af
206 changed files with 13831 additions and 4132 deletions

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -38,7 +38,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/clock"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/client-go/pkg/apis/clientauthentication"
"k8s.io/client-go/pkg/apis/clientauthentication/install"
clientauthenticationv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1"
@@ -49,6 +49,7 @@ import (
"k8s.io/client-go/transport"
"k8s.io/client-go/util/connrotation"
"k8s.io/klog/v2"
"k8s.io/utils/clock"
)
const execInfoEnv = "KUBERNETES_EXEC_INFO"
@@ -316,11 +317,17 @@ func (a *Authenticator) UpdateTransportConfig(c *transport.Config) error {
return nil
}
var _ utilnet.RoundTripperWrapper = &roundTripper{}
type roundTripper struct {
a *Authenticator
base http.RoundTripper
}
func (r *roundTripper) WrappedRoundTripper() http.RoundTripper {
return r.base
}
func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// If a user has already set credentials, use that. This makes commands like
// "kubectl get --token (token) pods" work.

View File

@@ -202,6 +202,8 @@ func (c *Config) String() string {
type ImpersonationConfig struct {
// UserName is the username to impersonate on each request.
UserName string
// UID is a unique value that identifies the user.
UID string
// Groups are the groups to impersonate on each request.
Groups []string
// Extra is a free-form field which can be used to link some authentication information
@@ -303,6 +305,8 @@ type ContentConfig struct {
// object. Note that a RESTClient may require fields that are optional when initializing a Client.
// A RESTClient created by this method is generic - it expects to operate on an API that follows
// the Kubernetes conventions, but may not be the Kubernetes API.
// RESTClientFor is equivalent to calling RESTClientForConfigAndClient(config, httpClient),
// where httpClient was generated with HTTPClientFor(config).
func RESTClientFor(config *Config) (*RESTClient, error) {
if config.GroupVersion == nil {
return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient")
@@ -311,24 +315,40 @@ func RESTClientFor(config *Config) (*RESTClient, error) {
return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
}
// Validate config.Host before constructing the transport/client so we can fail fast.
// ServerURL will be obtained later in RESTClientForConfigAndClient()
_, _, err := defaultServerUrlFor(config)
if err != nil {
return nil, err
}
httpClient, err := HTTPClientFor(config)
if err != nil {
return nil, err
}
return RESTClientForConfigAndClient(config, httpClient)
}
// RESTClientForConfigAndClient returns a RESTClient that satisfies the requested attributes on a
// client Config object.
// Unlike RESTClientFor, RESTClientForConfigAndClient allows to pass an http.Client that is shared
// between all the API Groups and Versions.
// Note that the http client takes precedence over the transport values configured.
// The http client defaults to the `http.DefaultClient` if nil.
func RESTClientForConfigAndClient(config *Config, httpClient *http.Client) (*RESTClient, error) {
if config.GroupVersion == nil {
return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient")
}
if config.NegotiatedSerializer == nil {
return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
}
baseURL, versionedAPIPath, err := defaultServerUrlFor(config)
if err != nil {
return nil, err
}
transport, err := TransportFor(config)
if err != nil {
return nil, err
}
var httpClient *http.Client
if transport != http.DefaultTransport {
httpClient = &http.Client{Transport: transport}
if config.Timeout > 0 {
httpClient.Timeout = config.Timeout
}
}
rateLimiter := config.RateLimiter
if rateLimiter == nil {
qps := config.QPS
@@ -369,24 +389,33 @@ func UnversionedRESTClientFor(config *Config) (*RESTClient, error) {
return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
}
// Validate config.Host before constructing the transport/client so we can fail fast.
// ServerURL will be obtained later in UnversionedRESTClientForConfigAndClient()
_, _, err := defaultServerUrlFor(config)
if err != nil {
return nil, err
}
httpClient, err := HTTPClientFor(config)
if err != nil {
return nil, err
}
return UnversionedRESTClientForConfigAndClient(config, httpClient)
}
// UnversionedRESTClientForConfigAndClient is the same as RESTClientForConfigAndClient,
// except that it allows the config.Version to be empty.
func UnversionedRESTClientForConfigAndClient(config *Config, httpClient *http.Client) (*RESTClient, error) {
if config.NegotiatedSerializer == nil {
return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
}
baseURL, versionedAPIPath, err := defaultServerUrlFor(config)
if err != nil {
return nil, err
}
transport, err := TransportFor(config)
if err != nil {
return nil, err
}
var httpClient *http.Client
if transport != http.DefaultTransport {
httpClient = &http.Client{Transport: transport}
if config.Timeout > 0 {
httpClient.Timeout = config.Timeout
}
}
rateLimiter := config.RateLimiter
if rateLimiter == nil {
qps := config.QPS
@@ -608,9 +637,10 @@ func CopyConfig(config *Config) *Config {
BearerToken: config.BearerToken,
BearerTokenFile: config.BearerTokenFile,
Impersonate: ImpersonationConfig{
UserName: config.Impersonate.UserName,
UID: config.Impersonate.UID,
Groups: config.Impersonate.Groups,
Extra: config.Impersonate.Extra,
UserName: config.Impersonate.UserName,
},
AuthProvider: config.AuthProvider,
AuthConfigPersister: config.AuthConfigPersister,

View File

@@ -39,13 +39,13 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
utilclock "k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/watch"
restclientwatch "k8s.io/client-go/rest/watch"
"k8s.io/client-go/tools/metrics"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog/v2"
"k8s.io/utils/clock"
)
var (
@@ -619,12 +619,12 @@ type throttleSettings struct {
}
type throttledLogger struct {
clock utilclock.PassiveClock
clock clock.PassiveClock
settings []*throttleSettings
}
var globalThrottledLogger = &throttledLogger{
clock: utilclock.RealClock{},
clock: clock.RealClock{},
settings: []*throttleSettings{
{
logLevel: 2,

View File

@@ -26,6 +26,27 @@ import (
"k8s.io/client-go/transport"
)
// HTTPClientFor returns an http.Client that will provide the authentication
// or transport level security defined by the provided Config. Will return the
// default http.DefaultClient if no special case behavior is needed.
func HTTPClientFor(config *Config) (*http.Client, error) {
transport, err := TransportFor(config)
if err != nil {
return nil, err
}
var httpClient *http.Client
if transport != http.DefaultTransport || config.Timeout > 0 {
httpClient = &http.Client{
Transport: transport,
Timeout: config.Timeout,
}
} else {
httpClient = http.DefaultClient
}
return httpClient, nil
}
// TLSConfigFor returns a tls.Config that will provide the transport level security defined
// by the provided Config. Will return nil if no transport level security is requested.
func TLSConfigFor(config *Config) (*tls.Config, error) {
@@ -83,6 +104,7 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
BearerTokenFile: c.BearerTokenFile,
Impersonate: transport.ImpersonationConfig{
UserName: c.Impersonate.UserName,
UID: c.Impersonate.UID,
Groups: c.Impersonate.Groups,
Extra: c.Impersonate.Extra,
},

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -124,7 +124,10 @@ type AuthInfo struct {
// Impersonate is the username to act-as.
// +optional
Impersonate string `json:"act-as,omitempty"`
// ImpersonateGroups is the groups to imperonate.
// ImpersonateUID is the uid to impersonate.
// +optional
ImpersonateUID string `json:"act-as-uid,omitempty"`
// ImpersonateGroups is the groups to impersonate.
// +optional
ImpersonateGroups []string `json:"act-as-groups,omitempty"`
// ImpersonateUserExtra contains additional information for impersonated user.

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -82,6 +82,8 @@ type Config struct {
type ImpersonationConfig struct {
// UserName matches user.Info.GetName()
UserName string
// UID matches user.Info.GetUID()
UID string
// Groups matches user.Info.GetGroups()
Groups []string
// Extra matches user.Info.GetExtra()

View File

@@ -17,9 +17,12 @@ limitations under the License.
package transport
import (
"crypto/tls"
"fmt"
"net/http"
"net/http/httptrace"
"strings"
"sync"
"time"
"golang.org/x/oauth2"
@@ -57,6 +60,7 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip
rt = NewUserAgentRoundTripper(config.UserAgent, rt)
}
if len(config.Impersonate.UserName) > 0 ||
len(config.Impersonate.UID) > 0 ||
len(config.Impersonate.Groups) > 0 ||
len(config.Impersonate.Extra) > 0 {
rt = NewImpersonatingRoundTripper(config.Impersonate, rt)
@@ -68,7 +72,7 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip
func DebugWrappers(rt http.RoundTripper) http.RoundTripper {
switch {
case bool(klog.V(9).Enabled()):
rt = NewDebuggingRoundTripper(rt, DebugCurlCommand, DebugURLTiming, DebugResponseHeaders)
rt = NewDebuggingRoundTripper(rt, DebugCurlCommand, DebugDetailedTiming, DebugResponseHeaders)
case bool(klog.V(8).Enabled()):
rt = NewDebuggingRoundTripper(rt, DebugJustURL, DebugRequestHeaders, DebugResponseStatus, DebugResponseHeaders)
case bool(klog.V(7).Enabled()):
@@ -88,6 +92,8 @@ type authProxyRoundTripper struct {
rt http.RoundTripper
}
var _ utilnet.RoundTripperWrapper = &authProxyRoundTripper{}
// NewAuthProxyRoundTripper provides a roundtripper which will add auth proxy fields to requests for
// authentication terminating proxy cases
// assuming you pull the user from the context:
@@ -146,6 +152,8 @@ type userAgentRoundTripper struct {
rt http.RoundTripper
}
var _ utilnet.RoundTripperWrapper = &userAgentRoundTripper{}
// NewUserAgentRoundTripper will add User-Agent header to a request unless it has already been set.
func NewUserAgentRoundTripper(agent string, rt http.RoundTripper) http.RoundTripper {
return &userAgentRoundTripper{agent, rt}
@@ -172,6 +180,8 @@ type basicAuthRoundTripper struct {
rt http.RoundTripper
}
var _ utilnet.RoundTripperWrapper = &basicAuthRoundTripper{}
// NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a
// request unless it has already been set.
func NewBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper {
@@ -199,6 +209,9 @@ const (
// ImpersonateUserHeader is used to impersonate a particular user during an API server request
ImpersonateUserHeader = "Impersonate-User"
// ImpersonateUIDHeader is used to impersonate a particular UID during an API server request
ImpersonateUIDHeader = "Impersonate-Uid"
// ImpersonateGroupHeader is used to impersonate a particular group during an API server request.
// It can be repeated multiplied times for multiple groups.
ImpersonateGroupHeader = "Impersonate-Group"
@@ -218,6 +231,8 @@ type impersonatingRoundTripper struct {
delegate http.RoundTripper
}
var _ utilnet.RoundTripperWrapper = &impersonatingRoundTripper{}
// NewImpersonatingRoundTripper will add an Act-As header to a request unless it has already been set.
func NewImpersonatingRoundTripper(impersonate ImpersonationConfig, delegate http.RoundTripper) http.RoundTripper {
return &impersonatingRoundTripper{impersonate, delegate}
@@ -230,7 +245,9 @@ func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Respons
}
req = utilnet.CloneRequest(req)
req.Header.Set(ImpersonateUserHeader, rt.impersonate.UserName)
if rt.impersonate.UID != "" {
req.Header.Set(ImpersonateUIDHeader, rt.impersonate.UID)
}
for _, group := range rt.impersonate.Groups {
req.Header.Add(ImpersonateGroupHeader, group)
}
@@ -255,6 +272,8 @@ type bearerAuthRoundTripper struct {
rt http.RoundTripper
}
var _ utilnet.RoundTripperWrapper = &bearerAuthRoundTripper{}
// NewBearerAuthRoundTripper adds the provided bearer token to a request
// unless the authorization header has already been set.
func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
@@ -314,6 +333,14 @@ type requestInfo struct {
ResponseHeaders http.Header
ResponseErr error
muTrace sync.Mutex // Protect trace fields
DNSLookup time.Duration
Dialing time.Duration
GetConnection time.Duration
TLSHandshake time.Duration
ServerProcessing time.Duration
ConnectionReused bool
Duration time.Duration
}
@@ -356,6 +383,8 @@ type debuggingRoundTripper struct {
levels map[DebugLevel]bool
}
var _ utilnet.RoundTripperWrapper = &debuggingRoundTripper{}
// DebugLevel is used to enable debugging of certain
// HTTP requests and responses fields via the debuggingRoundTripper.
type DebugLevel int
@@ -374,6 +403,8 @@ const (
DebugResponseStatus
// DebugResponseHeaders will add to the debug output the HTTP response headers.
DebugResponseHeaders
// DebugDetailedTiming will add to the debug output the duration of the HTTP requests events.
DebugDetailedTiming
)
// NewDebuggingRoundTripper allows to display in the logs output debug information
@@ -445,6 +476,74 @@ func (rt *debuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, e
}
startTime := time.Now()
if rt.levels[DebugDetailedTiming] {
var getConn, dnsStart, dialStart, tlsStart, serverStart time.Time
var host string
trace := &httptrace.ClientTrace{
// DNS
DNSStart: func(info httptrace.DNSStartInfo) {
reqInfo.muTrace.Lock()
defer reqInfo.muTrace.Unlock()
dnsStart = time.Now()
host = info.Host
},
DNSDone: func(info httptrace.DNSDoneInfo) {
reqInfo.muTrace.Lock()
defer reqInfo.muTrace.Unlock()
reqInfo.DNSLookup = time.Now().Sub(dnsStart)
klog.Infof("HTTP Trace: DNS Lookup for %s resolved to %v", host, info.Addrs)
},
// Dial
ConnectStart: func(network, addr string) {
reqInfo.muTrace.Lock()
defer reqInfo.muTrace.Unlock()
dialStart = time.Now()
},
ConnectDone: func(network, addr string, err error) {
reqInfo.muTrace.Lock()
defer reqInfo.muTrace.Unlock()
reqInfo.Dialing = time.Now().Sub(dialStart)
if err != nil {
klog.Infof("HTTP Trace: Dial to %s:%s failed: %v", network, addr, err)
} else {
klog.Infof("HTTP Trace: Dial to %s:%s succeed", network, addr)
}
},
// TLS
TLSHandshakeStart: func() {
tlsStart = time.Now()
},
TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
reqInfo.muTrace.Lock()
defer reqInfo.muTrace.Unlock()
reqInfo.TLSHandshake = time.Now().Sub(tlsStart)
},
// Connection (it can be DNS + Dial or just the time to get one from the connection pool)
GetConn: func(hostPort string) {
getConn = time.Now()
},
GotConn: func(info httptrace.GotConnInfo) {
reqInfo.muTrace.Lock()
defer reqInfo.muTrace.Unlock()
reqInfo.GetConnection = time.Now().Sub(getConn)
reqInfo.ConnectionReused = info.Reused
},
// Server Processing (time since we wrote the request until first byte is received)
WroteRequest: func(info httptrace.WroteRequestInfo) {
reqInfo.muTrace.Lock()
defer reqInfo.muTrace.Unlock()
serverStart = time.Now()
},
GotFirstResponseByte: func() {
reqInfo.muTrace.Lock()
defer reqInfo.muTrace.Unlock()
reqInfo.ServerProcessing = time.Now().Sub(serverStart)
},
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
}
response, err := rt.delegatedRoundTripper.RoundTrip(req)
reqInfo.Duration = time.Since(startTime)
@@ -453,6 +552,24 @@ func (rt *debuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, e
if rt.levels[DebugURLTiming] {
klog.Infof("%s %s %s in %d milliseconds", reqInfo.RequestVerb, reqInfo.RequestURL, reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
}
if rt.levels[DebugDetailedTiming] {
stats := ""
if !reqInfo.ConnectionReused {
stats += fmt.Sprintf(`DNSLookup %d ms Dial %d ms TLSHandshake %d ms`,
reqInfo.DNSLookup.Nanoseconds()/int64(time.Millisecond),
reqInfo.Dialing.Nanoseconds()/int64(time.Millisecond),
reqInfo.TLSHandshake.Nanoseconds()/int64(time.Millisecond),
)
} else {
stats += fmt.Sprintf(`GetConnection %d ms`, reqInfo.GetConnection.Nanoseconds()/int64(time.Millisecond))
}
if reqInfo.ServerProcessing != 0 {
stats += fmt.Sprintf(` ServerProcessing %d ms`, reqInfo.ServerProcessing.Nanoseconds()/int64(time.Millisecond))
}
stats += fmt.Sprintf(` Duration %d ms`, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
klog.Infof("HTTP Statistics: %s", stats)
}
if rt.levels[DebugResponseStatus] {
klog.Infof("Response Status: %s in %d milliseconds", reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
}

View File

@@ -26,6 +26,7 @@ import (
"golang.org/x/oauth2"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/klog/v2"
)
@@ -95,6 +96,8 @@ type tokenSourceTransport struct {
src ResettableTokenSource
}
var _ utilnet.RoundTripperWrapper = &tokenSourceTransport{}
func (tst *tokenSourceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// This is to allow --token to override other bearer token providers.
if req.Header.Get("Authorization") != "" {
@@ -119,6 +122,8 @@ func (tst *tokenSourceTransport) CancelRequest(req *http.Request) {
tryCancelRequest(tst.ort, req)
}
func (tst *tokenSourceTransport) WrappedRoundTripper() http.RoundTripper { return tst.base }
type fileTokenSource struct {
path string
period time.Duration

View File

@@ -33,6 +33,7 @@ import (
"time"
"k8s.io/client-go/util/keyutil"
netutils "k8s.io/utils/net"
)
const duration365d = time.Hour * 24 * 365
@@ -157,7 +158,7 @@ func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, a
BasicConstraintsValid: true,
}
if ip := net.ParseIP(host); ip != nil {
if ip := netutils.ParseIPSloppy(host); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, host)

View File

@@ -17,10 +17,12 @@ limitations under the License.
package flowcontrol
import (
"math/rand"
"sync"
"time"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/utils/clock"
testingclock "k8s.io/utils/clock/testing"
"k8s.io/utils/integer"
)
@@ -35,23 +37,43 @@ type Backoff struct {
defaultDuration time.Duration
maxDuration time.Duration
perItemBackoff map[string]*backoffEntry
rand *rand.Rand
// maxJitterFactor adds jitter to the exponentially backed off delay.
// if maxJitterFactor is zero, no jitter is added to the delay in
// order to maintain current behavior.
maxJitterFactor float64
}
func NewFakeBackOff(initial, max time.Duration, tc *clock.FakeClock) *Backoff {
return &Backoff{
perItemBackoff: map[string]*backoffEntry{},
Clock: tc,
defaultDuration: initial,
maxDuration: max,
}
func NewFakeBackOff(initial, max time.Duration, tc *testingclock.FakeClock) *Backoff {
return newBackoff(tc, initial, max, 0.0)
}
func NewBackOff(initial, max time.Duration) *Backoff {
return NewBackOffWithJitter(initial, max, 0.0)
}
func NewFakeBackOffWithJitter(initial, max time.Duration, tc *testingclock.FakeClock, maxJitterFactor float64) *Backoff {
return newBackoff(tc, initial, max, maxJitterFactor)
}
func NewBackOffWithJitter(initial, max time.Duration, maxJitterFactor float64) *Backoff {
clock := clock.RealClock{}
return newBackoff(clock, initial, max, maxJitterFactor)
}
func newBackoff(clock clock.Clock, initial, max time.Duration, maxJitterFactor float64) *Backoff {
var random *rand.Rand
if maxJitterFactor > 0 {
random = rand.New(rand.NewSource(clock.Now().UnixNano()))
}
return &Backoff{
perItemBackoff: map[string]*backoffEntry{},
Clock: clock.RealClock{},
Clock: clock,
defaultDuration: initial,
maxDuration: max,
maxJitterFactor: maxJitterFactor,
rand: random,
}
}
@@ -74,8 +96,10 @@ func (p *Backoff) Next(id string, eventTime time.Time) {
entry, ok := p.perItemBackoff[id]
if !ok || hasExpired(eventTime, entry.lastUpdate, p.maxDuration) {
entry = p.initEntryUnsafe(id)
entry.backoff += p.jitter(entry.backoff)
} else {
delay := entry.backoff * 2 // exponential
delay := entry.backoff * 2 // exponential
delay += p.jitter(entry.backoff) // add some jitter to the delay
entry.backoff = time.Duration(integer.Int64Min(int64(delay), int64(p.maxDuration)))
}
entry.lastUpdate = p.Clock.Now()
@@ -143,6 +167,14 @@ func (p *Backoff) initEntryUnsafe(id string) *backoffEntry {
return entry
}
func (p *Backoff) jitter(delay time.Duration) time.Duration {
if p.rand == nil {
return 0
}
return time.Duration(p.rand.Float64() * p.maxJitterFactor * float64(delay))
}
// After 2*maxDuration we restart the backoff factor to the beginning
func hasExpired(eventTime time.Time, lastUpdate time.Time, maxDuration time.Duration) bool {
return eventTime.Sub(lastUpdate) > maxDuration*2 // consider stable if it's ok for twice the maxDuration

View File

@@ -23,26 +23,36 @@ import (
"time"
"golang.org/x/time/rate"
"k8s.io/utils/clock"
)
type RateLimiter interface {
type PassiveRateLimiter interface {
// TryAccept returns true if a token is taken immediately. Otherwise,
// it returns false.
TryAccept() bool
// Accept returns once a token becomes available.
Accept()
// Stop stops the rate limiter, subsequent calls to CanAccept will return false
Stop()
// QPS returns QPS of this rate limiter
QPS() float32
}
type RateLimiter interface {
PassiveRateLimiter
// Accept returns once a token becomes available.
Accept()
// Wait returns nil if a token is taken before the Context is done.
Wait(ctx context.Context) error
}
type tokenBucketRateLimiter struct {
type tokenBucketPassiveRateLimiter struct {
limiter *rate.Limiter
clock Clock
qps float32
clock clock.PassiveClock
}
type tokenBucketRateLimiter struct {
tokenBucketPassiveRateLimiter
clock Clock
}
// NewTokenBucketRateLimiter creates a rate limiter which implements a token bucket approach.
@@ -52,58 +62,73 @@ type tokenBucketRateLimiter struct {
// The maximum number of tokens in the bucket is capped at 'burst'.
func NewTokenBucketRateLimiter(qps float32, burst int) RateLimiter {
limiter := rate.NewLimiter(rate.Limit(qps), burst)
return newTokenBucketRateLimiter(limiter, realClock{}, qps)
return newTokenBucketRateLimiterWithClock(limiter, clock.RealClock{}, qps)
}
// NewTokenBucketPassiveRateLimiter is similar to NewTokenBucketRateLimiter except that it returns
// a PassiveRateLimiter which does not have Accept() and Wait() methods.
func NewTokenBucketPassiveRateLimiter(qps float32, burst int) PassiveRateLimiter {
limiter := rate.NewLimiter(rate.Limit(qps), burst)
return newTokenBucketRateLimiterWithPassiveClock(limiter, clock.RealClock{}, qps)
}
// An injectable, mockable clock interface.
type Clock interface {
Now() time.Time
clock.PassiveClock
Sleep(time.Duration)
}
type realClock struct{}
func (realClock) Now() time.Time {
return time.Now()
}
func (realClock) Sleep(d time.Duration) {
time.Sleep(d)
}
var _ Clock = (*clock.RealClock)(nil)
// NewTokenBucketRateLimiterWithClock is identical to NewTokenBucketRateLimiter
// but allows an injectable clock, for testing.
func NewTokenBucketRateLimiterWithClock(qps float32, burst int, c Clock) RateLimiter {
limiter := rate.NewLimiter(rate.Limit(qps), burst)
return newTokenBucketRateLimiter(limiter, c, qps)
return newTokenBucketRateLimiterWithClock(limiter, c, qps)
}
func newTokenBucketRateLimiter(limiter *rate.Limiter, c Clock, qps float32) RateLimiter {
// NewTokenBucketPassiveRateLimiterWithClock is similar to NewTokenBucketRateLimiterWithClock
// except that it returns a PassiveRateLimiter which does not have Accept() and Wait() methods
// and uses a PassiveClock.
func NewTokenBucketPassiveRateLimiterWithClock(qps float32, burst int, c clock.PassiveClock) PassiveRateLimiter {
limiter := rate.NewLimiter(rate.Limit(qps), burst)
return newTokenBucketRateLimiterWithPassiveClock(limiter, c, qps)
}
func newTokenBucketRateLimiterWithClock(limiter *rate.Limiter, c Clock, qps float32) *tokenBucketRateLimiter {
return &tokenBucketRateLimiter{
limiter: limiter,
clock: c,
qps: qps,
tokenBucketPassiveRateLimiter: *newTokenBucketRateLimiterWithPassiveClock(limiter, c, qps),
clock: c,
}
}
func (t *tokenBucketRateLimiter) TryAccept() bool {
return t.limiter.AllowN(t.clock.Now(), 1)
func newTokenBucketRateLimiterWithPassiveClock(limiter *rate.Limiter, c clock.PassiveClock, qps float32) *tokenBucketPassiveRateLimiter {
return &tokenBucketPassiveRateLimiter{
limiter: limiter,
qps: qps,
clock: c,
}
}
func (tbprl *tokenBucketPassiveRateLimiter) Stop() {
}
func (tbprl *tokenBucketPassiveRateLimiter) QPS() float32 {
return tbprl.qps
}
func (tbprl *tokenBucketPassiveRateLimiter) TryAccept() bool {
return tbprl.limiter.AllowN(tbprl.clock.Now(), 1)
}
// Accept will block until a token becomes available
func (t *tokenBucketRateLimiter) Accept() {
now := t.clock.Now()
t.clock.Sleep(t.limiter.ReserveN(now, 1).DelayFrom(now))
func (tbrl *tokenBucketRateLimiter) Accept() {
now := tbrl.clock.Now()
tbrl.clock.Sleep(tbrl.limiter.ReserveN(now, 1).DelayFrom(now))
}
func (t *tokenBucketRateLimiter) Stop() {
}
func (t *tokenBucketRateLimiter) QPS() float32 {
return t.qps
}
func (t *tokenBucketRateLimiter) Wait(ctx context.Context) error {
return t.limiter.Wait(ctx)
func (tbrl *tokenBucketRateLimiter) Wait(ctx context.Context) error {
return tbrl.limiter.Wait(ctx)
}
type fakeAlwaysRateLimiter struct{}
@@ -157,3 +182,11 @@ func (t *fakeNeverRateLimiter) QPS() float32 {
func (t *fakeNeverRateLimiter) Wait(ctx context.Context) error {
return errors.New("can not be accept")
}
var (
_ RateLimiter = (*tokenBucketRateLimiter)(nil)
_ RateLimiter = (*fakeAlwaysRateLimiter)(nil)
_ RateLimiter = (*fakeNeverRateLimiter)(nil)
)
var _ PassiveRateLimiter = (*tokenBucketPassiveRateLimiter)(nil)

View File

@@ -27,7 +27,7 @@ import (
type RateLimiter interface {
// When gets an item and gets to decide how long that item should wait
When(item interface{}) time.Duration
// Forget indicates that an item is finished being retried. Doesn't matter whether its for perm failing
// Forget indicates that an item is finished being retried. Doesn't matter whether it's for failing
// or for success, we'll stop tracking it
Forget(item interface{})
// NumRequeues returns back how many failures the item has had
@@ -209,3 +209,30 @@ func (r *MaxOfRateLimiter) Forget(item interface{}) {
limiter.Forget(item)
}
}
// WithMaxWaitRateLimiter have maxDelay which avoids waiting too long
type WithMaxWaitRateLimiter struct {
limiter RateLimiter
maxDelay time.Duration
}
func NewWithMaxWaitRateLimiter(limiter RateLimiter, maxDelay time.Duration) RateLimiter {
return &WithMaxWaitRateLimiter{limiter: limiter, maxDelay: maxDelay}
}
func (w WithMaxWaitRateLimiter) When(item interface{}) time.Duration {
delay := w.limiter.When(item)
if delay > w.maxDelay {
return w.maxDelay
}
return delay
}
func (w WithMaxWaitRateLimiter) Forget(item interface{}) {
w.limiter.Forget(item)
}
func (w WithMaxWaitRateLimiter) NumRequeues(item interface{}) int {
return w.limiter.NumRequeues(item)
}

View File

@@ -21,8 +21,8 @@ import (
"sync"
"time"
"k8s.io/apimachinery/pkg/util/clock"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/utils/clock"
)
// DelayingInterface is an Interface that can Add an item at a later time. This makes it easier to
@@ -51,11 +51,11 @@ func NewNamedDelayingQueue(name string) DelayingInterface {
// NewDelayingQueueWithCustomClock constructs a new named workqueue
// with ability to inject real or fake clock for testing purposes
func NewDelayingQueueWithCustomClock(clock clock.Clock, name string) DelayingInterface {
func NewDelayingQueueWithCustomClock(clock clock.WithTicker, name string) DelayingInterface {
return newDelayingQueue(clock, NewNamed(name), name)
}
func newDelayingQueue(clock clock.Clock, q Interface, name string) *delayingType {
func newDelayingQueue(clock clock.WithTicker, q Interface, name string) *delayingType {
ret := &delayingType{
Interface: q,
clock: clock,

View File

@@ -20,7 +20,7 @@ import (
"sync"
"time"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/utils/clock"
)
// This file provides abstractions for setting the provider (e.g., prometheus)

View File

@@ -20,7 +20,7 @@ import (
"sync"
"time"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/utils/clock"
)
type Interface interface {
@@ -29,6 +29,7 @@ type Interface interface {
Get() (item interface{}, shutdown bool)
Done(item interface{})
ShutDown()
ShutDownWithDrain()
ShuttingDown() bool
}
@@ -46,7 +47,7 @@ func NewNamed(name string) *Type {
)
}
func newQueue(c clock.Clock, metrics queueMetrics, updatePeriod time.Duration) *Type {
func newQueue(c clock.WithTicker, metrics queueMetrics, updatePeriod time.Duration) *Type {
t := &Type{
clock: c,
dirty: set{},
@@ -86,11 +87,12 @@ type Type struct {
cond *sync.Cond
shuttingDown bool
drain bool
metrics queueMetrics
unfinishedWorkUpdatePeriod time.Duration
clock clock.Clock
clock clock.WithTicker
}
type empty struct{}
@@ -110,6 +112,10 @@ func (s set) delete(item t) {
delete(s, item)
}
func (s set) len() int {
return len(s)
}
// Add marks item as needing processing.
func (q *Type) Add(item interface{}) {
q.cond.L.Lock()
@@ -155,7 +161,10 @@ func (q *Type) Get() (item interface{}, shutdown bool) {
return nil, true
}
item, q.queue = q.queue[0], q.queue[1:]
item = q.queue[0]
// The underlying array still exists and reference this object, so the object will not be garbage collected.
q.queue[0] = nil
q.queue = q.queue[1:]
q.metrics.get(item)
@@ -178,13 +187,71 @@ func (q *Type) Done(item interface{}) {
if q.dirty.has(item) {
q.queue = append(q.queue, item)
q.cond.Signal()
} else if q.processing.len() == 0 {
q.cond.Signal()
}
}
// ShutDown will cause q to ignore all new items added to it. As soon as the
// worker goroutines have drained the existing items in the queue, they will be
// instructed to exit.
// ShutDown will cause q to ignore all new items added to it and
// immediately instruct the worker goroutines to exit.
func (q *Type) ShutDown() {
q.setDrain(false)
q.shutdown()
}
// ShutDownWithDrain will cause q to ignore all new items added to it. As soon
// as the worker goroutines have "drained", i.e: finished processing and called
// Done on all existing items in the queue; they will be instructed to exit and
// ShutDownWithDrain will return. Hence: a strict requirement for using this is;
// your workers must ensure that Done is called on all items in the queue once
// the shut down has been initiated, if that is not the case: this will block
// indefinitely. It is, however, safe to call ShutDown after having called
// ShutDownWithDrain, as to force the queue shut down to terminate immediately
// without waiting for the drainage.
func (q *Type) ShutDownWithDrain() {
q.setDrain(true)
q.shutdown()
for q.isProcessing() && q.shouldDrain() {
q.waitForProcessing()
}
}
// isProcessing indicates if there are still items on the work queue being
// processed. It's used to drain the work queue on an eventual shutdown.
func (q *Type) isProcessing() bool {
q.cond.L.Lock()
defer q.cond.L.Unlock()
return q.processing.len() != 0
}
// waitForProcessing waits for the worker goroutines to finish processing items
// and call Done on them.
func (q *Type) waitForProcessing() {
q.cond.L.Lock()
defer q.cond.L.Unlock()
// Ensure that we do not wait on a queue which is already empty, as that
// could result in waiting for Done to be called on items in an empty queue
// which has already been shut down, which will result in waiting
// indefinitely.
if q.processing.len() == 0 {
return
}
q.cond.Wait()
}
func (q *Type) setDrain(shouldDrain bool) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
q.drain = shouldDrain
}
func (q *Type) shouldDrain() bool {
q.cond.L.Lock()
defer q.cond.L.Unlock()
return q.drain
}
func (q *Type) shutdown() {
q.cond.L.Lock()
defer q.cond.L.Unlock()
q.shuttingDown = true