Merge pull request #101905 from tkashem/apf-request-width

apf: add plumbing to calculate "width" of a request
This commit is contained in:
Kubernetes Prow Robot 2021-06-09 11:25:27 -07:00 committed by GitHub
commit 624967e940
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 137 additions and 29 deletions

View File

@ -64,6 +64,7 @@ import (
"k8s.io/apiserver/pkg/storageversion" "k8s.io/apiserver/pkg/storageversion"
"k8s.io/apiserver/pkg/util/feature" "k8s.io/apiserver/pkg/util/feature"
utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol" utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/component-base/logs" "k8s.io/component-base/logs"
@ -215,6 +216,9 @@ type Config struct {
// If not specify any in flags, then genericapiserver will only enable defaultAPIResourceConfig. // If not specify any in flags, then genericapiserver will only enable defaultAPIResourceConfig.
MergedResourceConfig *serverstore.ResourceConfig MergedResourceConfig *serverstore.ResourceConfig
// RequestWidthEstimator is used to estimate the "width" of the incoming request(s).
RequestWidthEstimator flowcontrolrequest.WidthEstimatorFunc
//=========================================================================== //===========================================================================
// values below here are targets for removal // values below here are targets for removal
//=========================================================================== //===========================================================================
@ -338,6 +342,8 @@ func NewConfig(codecs serializer.CodecFactory) *Config {
// Default to treating watch as a long-running operation // Default to treating watch as a long-running operation
// Generic API servers have no inherent long-running subresources // Generic API servers have no inherent long-running subresources
LongRunningFunc: genericfilters.BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString()), LongRunningFunc: genericfilters.BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString()),
RequestWidthEstimator: flowcontrolrequest.DefaultWidthEstimator,
APIServerID: id, APIServerID: id,
StorageVersionManager: storageversion.NewDefaultManager(), StorageVersionManager: storageversion.NewDefaultManager(),
} }
@ -728,7 +734,7 @@ func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
if c.FlowControl != nil { if c.FlowControl != nil {
handler = filterlatency.TrackCompleted(handler) handler = filterlatency.TrackCompleted(handler)
handler = genericfilters.WithPriorityAndFairness(handler, c.LongRunningFunc, c.FlowControl) handler = genericfilters.WithPriorityAndFairness(handler, c.LongRunningFunc, c.FlowControl, c.RequestWidthEstimator)
handler = filterlatency.TrackStarted(handler, "priorityandfairness") handler = filterlatency.TrackStarted(handler, "priorityandfairness")
} else { } else {
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.LongRunningFunc) handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.LongRunningFunc)

View File

@ -28,6 +28,7 @@ import (
apirequest "k8s.io/apiserver/pkg/endpoints/request" apirequest "k8s.io/apiserver/pkg/endpoints/request"
utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol" utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
fcmetrics "k8s.io/apiserver/pkg/util/flowcontrol/metrics" fcmetrics "k8s.io/apiserver/pkg/util/flowcontrol/metrics"
flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request"
"k8s.io/klog/v2" "k8s.io/klog/v2"
) )
@ -59,6 +60,7 @@ func WithPriorityAndFairness(
handler http.Handler, handler http.Handler,
longRunningRequestCheck apirequest.LongRunningRequestCheck, longRunningRequestCheck apirequest.LongRunningRequestCheck,
fcIfc utilflowcontrol.Interface, fcIfc utilflowcontrol.Interface,
widthEstimator flowcontrolrequest.WidthEstimatorFunc,
) http.Handler { ) http.Handler {
if fcIfc == nil { if fcIfc == nil {
klog.Warningf("priority and fairness support not found, skipping") klog.Warningf("priority and fairness support not found, skipping")
@ -159,7 +161,11 @@ func WithPriorityAndFairness(
handler.ServeHTTP(w, innerReq) handler.ServeHTTP(w, innerReq)
} }
} }
digest := utilflowcontrol.RequestDigest{RequestInfo: requestInfo, User: user}
// find the estimated "width" of the request
width := widthEstimator.EstimateWidth(r)
digest := utilflowcontrol.RequestDigest{RequestInfo: requestInfo, User: user, Width: width}
fcIfc.Handle(ctx, digest, note, func(inQueue bool) { fcIfc.Handle(ctx, digest, note, func(inQueue bool) {
if inQueue { if inQueue {
noteWaitingDelta(1) noteWaitingDelta(1)

View File

@ -50,6 +50,8 @@ import (
"k8s.io/component-base/metrics/legacyregistry" "k8s.io/component-base/metrics/legacyregistry"
"k8s.io/component-base/metrics/testutil" "k8s.io/component-base/metrics/testutil"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"github.com/google/go-cmp/cmp"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -67,6 +69,8 @@ const (
decisionSkipFilter decisionSkipFilter
) )
var defaultRequestWidthEstimator = func(*http.Request) uint { return 1 }
type fakeApfFilter struct { type fakeApfFilter struct {
mockDecision mockDecision mockDecision mockDecision
postEnqueue func() postEnqueue func()
@ -157,7 +161,7 @@ func newApfHandlerWithFilter(t *testing.T, flowControlFilter utilflowcontrol.Int
apfHandler := WithPriorityAndFairness(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { apfHandler := WithPriorityAndFairness(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
onExecute() onExecute()
}), longRunningRequestCheck, flowControlFilter) }), longRunningRequestCheck, flowControlFilter, defaultRequestWidthEstimator)
handler := apifilters.WithRequestInfo(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := apifilters.WithRequestInfo(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r = r.WithContext(apirequest.WithUser(r.Context(), &user.DefaultInfo{ r = r.WithContext(apirequest.WithUser(r.Context(), &user.DefaultInfo{
@ -562,6 +566,50 @@ func TestApfCancelWaitRequest(t *testing.T) {
}) })
} }
type fakeFilterRequestDigest struct {
*fakeApfFilter
requestDigestGot *utilflowcontrol.RequestDigest
}
func (f *fakeFilterRequestDigest) Handle(ctx context.Context,
requestDigest utilflowcontrol.RequestDigest,
_ func(fs *flowcontrol.FlowSchema, pl *flowcontrol.PriorityLevelConfiguration),
_ fq.QueueNoteFn, _ func(),
) {
f.requestDigestGot = &requestDigest
}
func TestApfWithRequestDigest(t *testing.T) {
longRunningFunc := func(_ *http.Request, _ *apirequest.RequestInfo) bool { return false }
fakeFilter := &fakeFilterRequestDigest{}
reqDigestExpected := &utilflowcontrol.RequestDigest{
RequestInfo: &apirequest.RequestInfo{Verb: "get"},
User: &user.DefaultInfo{Name: "foo"},
Width: 5,
}
handler := WithPriorityAndFairness(http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {}),
longRunningFunc,
fakeFilter,
func(_ *http.Request) uint { return reqDigestExpected.Width },
)
w := httptest.NewRecorder()
req, err := http.NewRequest(http.MethodGet, "/bar", nil)
if err != nil {
t.Fatalf("Failed to create new http request - %v", err)
}
req = req.WithContext(apirequest.WithRequestInfo(req.Context(), reqDigestExpected.RequestInfo))
req = req.WithContext(apirequest.WithUser(req.Context(), reqDigestExpected.User))
handler.ServeHTTP(w, req)
if !reflect.DeepEqual(reqDigestExpected, fakeFilter.requestDigestGot) {
t.Errorf("Expected RequestDigest to match, diff: %s", cmp.Diff(reqDigestExpected, fakeFilter.requestDigestGot))
}
}
func TestPriorityAndFairnessWithPanicRecoveryAndTimeoutFilter(t *testing.T) { func TestPriorityAndFairnessWithPanicRecoveryAndTimeoutFilter(t *testing.T) {
fcmetrics.Register() fcmetrics.Register()
@ -1058,7 +1106,7 @@ func newHandlerChain(t *testing.T, handler http.Handler, filter utilflowcontrol.
requestInfoFactory := &apirequest.RequestInfoFactory{APIPrefixes: sets.NewString("apis", "api"), GrouplessAPIPrefixes: sets.NewString("api")} requestInfoFactory := &apirequest.RequestInfoFactory{APIPrefixes: sets.NewString("apis", "api"), GrouplessAPIPrefixes: sets.NewString("api")}
longRunningRequestCheck := BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString("proxy")) longRunningRequestCheck := BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString("proxy"))
apfHandler := WithPriorityAndFairness(handler, longRunningRequestCheck, filter) apfHandler := WithPriorityAndFairness(handler, longRunningRequestCheck, filter, defaultRequestWidthEstimator)
// add the handler in the chain that adds the specified user to the request context // add the handler in the chain that adds the specified user to the request context
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

View File

@ -82,6 +82,7 @@ type StartFunction func(ctx context.Context, hashValue uint64) (execute bool, af
type RequestDigest struct { type RequestDigest struct {
RequestInfo *request.RequestInfo RequestInfo *request.RequestInfo
User user.Info User user.Info
Width uint
} }
// `*configController` maintains eventual consistency with the API // `*configController` maintains eventual consistency with the API
@ -804,7 +805,7 @@ func (cfgCtlr *configController) startRequest(ctx context.Context, rd RequestDig
} }
startWaitingTime = time.Now() startWaitingTime = time.Now()
klog.V(7).Infof("startRequest(%#+v) => fsName=%q, distMethod=%#+v, plName=%q, numQueues=%d", rd, selectedFlowSchema.Name, selectedFlowSchema.Spec.DistinguisherMethod, plName, numQueues) klog.V(7).Infof("startRequest(%#+v) => fsName=%q, distMethod=%#+v, plName=%q, numQueues=%d", rd, selectedFlowSchema.Name, selectedFlowSchema.Spec.DistinguisherMethod, plName, numQueues)
req, idle := plState.queues.StartRequest(ctx, hashValue, flowDistinguisher, selectedFlowSchema.Name, rd.RequestInfo, rd.User, queueNoteFn) req, idle := plState.queues.StartRequest(ctx, rd.Width, hashValue, flowDistinguisher, selectedFlowSchema.Name, rd.RequestInfo, rd.User, queueNoteFn)
if idle { if idle {
cfgCtlr.maybeReapLocked(plName, plState) cfgCtlr.maybeReapLocked(plName, plState)
} }

View File

@ -139,7 +139,7 @@ func (cqs *ctlrTestQueueSet) IsIdle() bool {
return cqs.countActive == 0 return cqs.countActive == 0
} }
func (cqs *ctlrTestQueueSet) StartRequest(ctx context.Context, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn fq.QueueNoteFn) (req fq.Request, idle bool) { func (cqs *ctlrTestQueueSet) StartRequest(ctx context.Context, width uint, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn fq.QueueNoteFn) (req fq.Request, idle bool) {
cqs.cts.lock.Lock() cqs.cts.lock.Lock()
defer cqs.cts.lock.Unlock() defer cqs.cts.lock.Unlock()
cqs.countActive++ cqs.countActive++

View File

@ -80,7 +80,7 @@ type QueueSet interface {
// was idle at the moment of the return. Otherwise idle==false // was idle at the moment of the return. Otherwise idle==false
// and the client must call the Finish method of the Request // and the client must call the Finish method of the Request
// exactly once. // exactly once.
StartRequest(ctx context.Context, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn QueueNoteFn) (req Request, idle bool) StartRequest(ctx context.Context, width uint, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn QueueNoteFn) (req Request, idle bool)
// UpdateObservations makes sure any time-based statistics have // UpdateObservations makes sure any time-based statistics have
// caught up with the current clock reading // caught up with the current clock reading

View File

@ -236,10 +236,7 @@ const (
// executing at each point where there is a change in that quantity, // executing at each point where there is a change in that quantity,
// because the metrics --- and only the metrics --- track that // because the metrics --- and only the metrics --- track that
// quantity per FlowSchema. // quantity per FlowSchema.
func (qs *queueSet) StartRequest(ctx context.Context, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn fq.QueueNoteFn) (fq.Request, bool) { func (qs *queueSet) StartRequest(ctx context.Context, width uint, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn fq.QueueNoteFn) (fq.Request, bool) {
// all request(s) have a width of 1, in keeping with the current behavior
width := 1.0
qs.lockAndSyncTime() qs.lockAndSyncTime()
defer qs.lock.Unlock() defer qs.lock.Unlock()
var req *request var req *request
@ -320,7 +317,7 @@ func (qs *queueSet) StartRequest(ctx context.Context, hashValue uint64, flowDist
// Seats returns the number of seats this request requires. // Seats returns the number of seats this request requires.
func (req *request) Seats() int { func (req *request) Seats() int {
return int(math.Ceil(req.width)) return int(req.width)
} }
func (req *request) NoteQueued(inQueue bool) { func (req *request) NoteQueued(inQueue bool) {
@ -440,7 +437,7 @@ func (qs *queueSet) getVirtualTimeRatioLocked() float64 {
// returns the enqueud request on a successful enqueue // returns the enqueud request on a successful enqueue
// returns nil in the case that there is no available concurrency or // returns nil in the case that there is no available concurrency or
// the queuelengthlimit has been reached // the queuelengthlimit has been reached
func (qs *queueSet) timeoutOldRequestsAndRejectOrEnqueueLocked(ctx context.Context, width float64, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn fq.QueueNoteFn) *request { func (qs *queueSet) timeoutOldRequestsAndRejectOrEnqueueLocked(ctx context.Context, width uint, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn fq.QueueNoteFn) *request {
// Start with the shuffle sharding, to pick a queue. // Start with the shuffle sharding, to pick a queue.
queueIdx := qs.chooseQueueIndexLocked(hashValue, descr1, descr2) queueIdx := qs.chooseQueueIndexLocked(hashValue, descr1, descr2)
queue := qs.queues[queueIdx] queue := qs.queues[queueIdx]
@ -578,7 +575,7 @@ func (qs *queueSet) dispatchAsMuchAsPossibleLocked() {
} }
} }
func (qs *queueSet) dispatchSansQueueLocked(ctx context.Context, width float64, flowDistinguisher, fsName string, descr1, descr2 interface{}) *request { func (qs *queueSet) dispatchSansQueueLocked(ctx context.Context, width uint, flowDistinguisher, fsName string, descr1, descr2 interface{}) *request {
now := qs.clock.Now() now := qs.clock.Now()
req := &request{ req := &request{
qs: qs, qs: qs,

View File

@ -226,7 +226,7 @@ func (ust *uniformScenarioThread) callK(k int) {
if k >= ust.nCalls { if k >= ust.nCalls {
return return
} }
req, idle := ust.uss.qs.StartRequest(context.Background(), ust.uc.hash, "", ust.fsName, ust.uss.name, []int{ust.i, ust.j, k}, nil) req, idle := ust.uss.qs.StartRequest(context.Background(), 1, ust.uc.hash, "", ust.fsName, ust.uss.name, []int{ust.i, ust.j, k}, nil)
ust.uss.t.Logf("%s: %d, %d, %d got req=%p, idle=%v", ust.uss.clk.Now().Format(nsTimeFmt), ust.i, ust.j, k, req, idle) ust.uss.t.Logf("%s: %d, %d, %d got req=%p, idle=%v", ust.uss.clk.Now().Format(nsTimeFmt), ust.i, ust.j, k, req, idle)
if req == nil { if req == nil {
atomic.AddUint64(&ust.uss.failedCount, 1) atomic.AddUint64(&ust.uss.failedCount, 1)
@ -658,7 +658,7 @@ func TestContextCancel(t *testing.T) {
ctx1 := context.Background() ctx1 := context.Background()
b2i := map[bool]int{false: 0, true: 1} b2i := map[bool]int{false: 0, true: 1}
var qnc [2][2]int32 var qnc [2][2]int32
req1, _ := qs.StartRequest(ctx1, 1, "", "fs1", "test", "one", func(inQueue bool) { atomic.AddInt32(&qnc[0][b2i[inQueue]], 1) }) req1, _ := qs.StartRequest(ctx1, 1, 1, "", "fs1", "test", "one", func(inQueue bool) { atomic.AddInt32(&qnc[0][b2i[inQueue]], 1) })
if req1 == nil { if req1 == nil {
t.Error("Request rejected") t.Error("Request rejected")
return return
@ -686,7 +686,7 @@ func TestContextCancel(t *testing.T) {
counter.Add(1) counter.Add(1)
cancel2() cancel2()
}() }()
req2, idle2a := qs.StartRequest(ctx2, 2, "", "fs2", "test", "two", func(inQueue bool) { atomic.AddInt32(&qnc[1][b2i[inQueue]], 1) }) req2, idle2a := qs.StartRequest(ctx2, 1, 2, "", "fs2", "test", "two", func(inQueue bool) { atomic.AddInt32(&qnc[1][b2i[inQueue]], 1) })
if idle2a { if idle2a {
t.Error("2nd StartRequest returned idle") t.Error("2nd StartRequest returned idle")
} }
@ -745,7 +745,7 @@ func TestTotalRequestsExecutingWithPanic(t *testing.T) {
} }
ctx := context.Background() ctx := context.Background()
req, _ := qs.StartRequest(ctx, 1, "", "fs", "test", "one", func(inQueue bool) {}) req, _ := qs.StartRequest(ctx, 1, 1, "", "fs", "test", "one", func(inQueue bool) {})
if req == nil { if req == nil {
t.Fatal("expected a Request object from StartRequest, but got nil") t.Fatal("expected a Request object from StartRequest, but got nil")
} }

View File

@ -44,7 +44,7 @@ type request struct {
startTime time.Time startTime time.Time
// width of the request // width of the request
width float64 width uint
// decision gets set to a `requestDecision` indicating what to do // decision gets set to a `requestDecision` indicating what to do
// with this request. It gets set exactly once, when the request // with this request. It gets set exactly once, when the request

View File

@ -55,7 +55,7 @@ func (noRestraint) IsIdle() bool {
return false return false
} }
func (noRestraint) StartRequest(ctx context.Context, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn fq.QueueNoteFn) (fq.Request, bool) { func (noRestraint) StartRequest(ctx context.Context, width uint, hashValue uint64, flowDistinguisher, fsName string, descr1, descr2 interface{}, queueNoteFn fq.QueueNoteFn) (fq.Request, bool) {
return noRestraintRequest{}, false return noRestraintRequest{}, false
} }

View File

@ -89,7 +89,7 @@ func TestLiterals(t *testing.T) {
ui := &user.DefaultInfo{Name: "goodu", UID: "1", ui := &user.DefaultInfo{Name: "goodu", UID: "1",
Groups: []string{"goodg1", "goodg2"}} Groups: []string{"goodg1", "goodg2"}}
reqRN := RequestDigest{ reqRN := RequestDigest{
&request.RequestInfo{ RequestInfo: &request.RequestInfo{
IsResourceRequest: true, IsResourceRequest: true,
Path: "/apis/goodapig/v1/namespaces/goodns/goodrscs", Path: "/apis/goodapig/v1/namespaces/goodns/goodrscs",
Verb: "goodverb", Verb: "goodverb",
@ -99,10 +99,13 @@ func TestLiterals(t *testing.T) {
Namespace: "goodns", Namespace: "goodns",
Resource: "goodrscs", Resource: "goodrscs",
Name: "eman", Name: "eman",
Parts: []string{"goodrscs", "eman"}}, Parts: []string{"goodrscs", "eman"},
ui} },
User: ui,
Width: 1,
}
reqRU := RequestDigest{ reqRU := RequestDigest{
&request.RequestInfo{ RequestInfo: &request.RequestInfo{
IsResourceRequest: true, IsResourceRequest: true,
Path: "/apis/goodapig/v1/goodrscs", Path: "/apis/goodapig/v1/goodrscs",
Verb: "goodverb", Verb: "goodverb",
@ -112,14 +115,20 @@ func TestLiterals(t *testing.T) {
Namespace: "", Namespace: "",
Resource: "goodrscs", Resource: "goodrscs",
Name: "eman", Name: "eman",
Parts: []string{"goodrscs", "eman"}}, Parts: []string{"goodrscs", "eman"},
ui} },
User: ui,
Width: 1,
}
reqN := RequestDigest{ reqN := RequestDigest{
&request.RequestInfo{ RequestInfo: &request.RequestInfo{
IsResourceRequest: false, IsResourceRequest: false,
Path: "/openapi/v2", Path: "/openapi/v2",
Verb: "goodverb"}, Verb: "goodverb",
ui} },
User: ui,
Width: 1,
}
checkRules(t, true, reqRN, []flowcontrol.PolicyRulesWithSubjects{{ checkRules(t, true, reqRN, []flowcontrol.PolicyRulesWithSubjects{{
Subjects: []flowcontrol.Subject{{Kind: flowcontrol.SubjectKindUser, Subjects: []flowcontrol.Subject{{Kind: flowcontrol.SubjectKindUser,
User: &flowcontrol.UserSubject{"goodu"}}}, User: &flowcontrol.UserSubject{"goodu"}}},

View File

@ -0,0 +1,40 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package request
import (
"net/http"
)
// DefaultWidthEstimator returns returns '1' as the "width"
// of the given request.
//
// TODO: when we plumb in actual "width" handling for different
// type of request(s) this function will iterate through a chain
// of widthEstimator instance(s).
func DefaultWidthEstimator(_ *http.Request) uint {
return 1
}
// WidthEstimatorFunc returns the estimated "width" of a given request.
// This function will be used by the Priority & Fairness filter to
// estimate the "width" of incoming requests.
type WidthEstimatorFunc func(*http.Request) uint
func (e WidthEstimatorFunc) EstimateWidth(r *http.Request) uint {
return e(r)
}

1
vendor/modules.txt vendored
View File

@ -1494,6 +1494,7 @@ k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/queueset
k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/testing k8s.io/apiserver/pkg/util/flowcontrol/fairqueuing/testing
k8s.io/apiserver/pkg/util/flowcontrol/format k8s.io/apiserver/pkg/util/flowcontrol/format
k8s.io/apiserver/pkg/util/flowcontrol/metrics k8s.io/apiserver/pkg/util/flowcontrol/metrics
k8s.io/apiserver/pkg/util/flowcontrol/request
k8s.io/apiserver/pkg/util/flushwriter k8s.io/apiserver/pkg/util/flushwriter
k8s.io/apiserver/pkg/util/openapi k8s.io/apiserver/pkg/util/openapi
k8s.io/apiserver/pkg/util/proxy k8s.io/apiserver/pkg/util/proxy