mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 18:43:34 +00:00
rebase: bump k8s.io/kubernetes from 1.26.2 to 1.27.2
Bumps [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes) from 1.26.2 to 1.27.2. - [Release notes](https://github.com/kubernetes/kubernetes/releases) - [Commits](https://github.com/kubernetes/kubernetes/compare/v1.26.2...v1.27.2) --- updated-dependencies: - dependency-name: k8s.io/kubernetes dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
mergify[bot]
parent
0e79135419
commit
07b05616a0
92
vendor/k8s.io/apiserver/pkg/util/flowcontrol/request/config.go
generated
vendored
Normal file
92
vendor/k8s.io/apiserver/pkg/util/flowcontrol/request/config.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
Copyright 2022 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 (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
minimumSeats = 1
|
||||
maximumSeats = 10
|
||||
objectsPerSeat = 100.0
|
||||
watchesPerSeat = 10.0
|
||||
enableMutatingWorkEstimator = true
|
||||
)
|
||||
|
||||
var eventAdditionalDuration = 5 * time.Millisecond
|
||||
|
||||
// WorkEstimatorConfig holds work estimator parameters.
|
||||
type WorkEstimatorConfig struct {
|
||||
*ListWorkEstimatorConfig `json:"listWorkEstimatorConfig,omitempty"`
|
||||
*MutatingWorkEstimatorConfig `json:"mutatingWorkEstimatorConfig,omitempty"`
|
||||
|
||||
// MinimumSeats is the minimum number of seats a request must occupy.
|
||||
MinimumSeats uint64 `json:"minimumSeats,omitempty"`
|
||||
// MaximumSeats is the maximum number of seats a request can occupy
|
||||
//
|
||||
// NOTE: work_estimate_seats_samples metric uses the value of maximumSeats
|
||||
// as the upper bound, so when we change maximumSeats we should also
|
||||
// update the buckets of the metric.
|
||||
MaximumSeats uint64 `json:"maximumSeats,omitempty"`
|
||||
}
|
||||
|
||||
// ListWorkEstimatorConfig holds work estimator parameters related to list requests.
|
||||
type ListWorkEstimatorConfig struct {
|
||||
ObjectsPerSeat float64 `json:"objectsPerSeat,omitempty"`
|
||||
}
|
||||
|
||||
// MutatingWorkEstimatorConfig holds work estimator
|
||||
// parameters related to watches of mutating objects.
|
||||
type MutatingWorkEstimatorConfig struct {
|
||||
// TODO(wojtekt): Remove it once we tune the algorithm to not fail
|
||||
// scalability tests.
|
||||
Enabled bool `json:"enable,omitempty"`
|
||||
EventAdditionalDuration metav1.Duration `json:"eventAdditionalDurationMs,omitempty"`
|
||||
WatchesPerSeat float64 `json:"watchesPerSeat,omitempty"`
|
||||
}
|
||||
|
||||
// DefaultWorkEstimatorConfig creates a new WorkEstimatorConfig with default values.
|
||||
func DefaultWorkEstimatorConfig() *WorkEstimatorConfig {
|
||||
return &WorkEstimatorConfig{
|
||||
MinimumSeats: minimumSeats,
|
||||
MaximumSeats: maximumSeats,
|
||||
ListWorkEstimatorConfig: defaultListWorkEstimatorConfig(),
|
||||
MutatingWorkEstimatorConfig: defaultMutatingWorkEstimatorConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
// defaultListWorkEstimatorConfig creates a new ListWorkEstimatorConfig with default values.
|
||||
func defaultListWorkEstimatorConfig() *ListWorkEstimatorConfig {
|
||||
return &ListWorkEstimatorConfig{ObjectsPerSeat: objectsPerSeat}
|
||||
}
|
||||
|
||||
// defaultMutatingWorkEstimatorConfig creates a new MutatingWorkEstimatorConfig with default values.
|
||||
func defaultMutatingWorkEstimatorConfig() *MutatingWorkEstimatorConfig {
|
||||
return &MutatingWorkEstimatorConfig{
|
||||
Enabled: enableMutatingWorkEstimator,
|
||||
EventAdditionalDuration: metav1.Duration{Duration: eventAdditionalDuration},
|
||||
WatchesPerSeat: watchesPerSeat,
|
||||
}
|
||||
}
|
||||
|
||||
// eventAdditionalDuration converts eventAdditionalDurationMs to a time.Duration type.
|
||||
func (c *MutatingWorkEstimatorConfig) eventAdditionalDuration() time.Duration {
|
||||
return c.EventAdditionalDuration.Duration
|
||||
}
|
154
vendor/k8s.io/apiserver/pkg/util/flowcontrol/request/list_work_estimator.go
generated
vendored
Normal file
154
vendor/k8s.io/apiserver/pkg/util/flowcontrol/request/list_work_estimator.go
generated
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
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 (
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func newListWorkEstimator(countFn objectCountGetterFunc, config *WorkEstimatorConfig) WorkEstimatorFunc {
|
||||
estimator := &listWorkEstimator{
|
||||
config: config,
|
||||
countGetterFn: countFn,
|
||||
}
|
||||
return estimator.estimate
|
||||
}
|
||||
|
||||
type listWorkEstimator struct {
|
||||
config *WorkEstimatorConfig
|
||||
countGetterFn objectCountGetterFunc
|
||||
}
|
||||
|
||||
func (e *listWorkEstimator) estimate(r *http.Request, flowSchemaName, priorityLevelName string) WorkEstimate {
|
||||
requestInfo, ok := apirequest.RequestInfoFrom(r.Context())
|
||||
if !ok {
|
||||
// no RequestInfo should never happen, but to be on the safe side
|
||||
// let's return maximumSeats
|
||||
return WorkEstimate{InitialSeats: e.config.MaximumSeats}
|
||||
}
|
||||
|
||||
if requestInfo.Name != "" {
|
||||
// Requests with metadata.name specified are usually executed as get
|
||||
// requests in storage layer so their width should be 1.
|
||||
// Example of such list requests:
|
||||
// /apis/certificates.k8s.io/v1/certificatesigningrequests?fieldSelector=metadata.name%3Dcsr-xxs4m
|
||||
// /api/v1/namespaces/test/configmaps?fieldSelector=metadata.name%3Dbig-deployment-1&limit=500&resourceVersion=0
|
||||
return WorkEstimate{InitialSeats: e.config.MinimumSeats}
|
||||
}
|
||||
|
||||
query := r.URL.Query()
|
||||
listOptions := metav1.ListOptions{}
|
||||
if err := metav1.Convert_url_Values_To_v1_ListOptions(&query, &listOptions, nil); err != nil {
|
||||
klog.ErrorS(err, "Failed to convert options while estimating work for the list request")
|
||||
|
||||
// This request is destined to fail in the validation layer,
|
||||
// return maximumSeats for this request to be consistent.
|
||||
return WorkEstimate{InitialSeats: e.config.MaximumSeats}
|
||||
}
|
||||
isListFromCache := !shouldListFromStorage(query, &listOptions)
|
||||
|
||||
numStored, err := e.countGetterFn(key(requestInfo))
|
||||
switch {
|
||||
case err == ObjectCountStaleErr:
|
||||
// object count going stale is indicative of degradation, so we should
|
||||
// be conservative here and allocate maximum seats to this list request.
|
||||
// NOTE: if a CRD is removed, its count will go stale first and then the
|
||||
// pruner will eventually remove the CRD from the cache.
|
||||
return WorkEstimate{InitialSeats: e.config.MaximumSeats}
|
||||
case err == ObjectCountNotFoundErr:
|
||||
// there are multiple scenarios in which we can see this error:
|
||||
// a. the type is truly unknown, a typo on the caller's part.
|
||||
// b. the count has gone stale for too long and the pruner
|
||||
// has removed the type from the cache.
|
||||
// c. the type is an aggregated resource that is served by a
|
||||
// different apiserver (thus its object count is not updated)
|
||||
// we don't have a way to distinguish between those situations.
|
||||
// However, in case c, the request is delegated to a different apiserver,
|
||||
// and thus its cost for our server is minimal. To avoid the situation
|
||||
// when aggregated API calls are overestimated, we allocate the minimum
|
||||
// possible seats (see #109106 as an example when being more conservative
|
||||
// led to problems).
|
||||
return WorkEstimate{InitialSeats: e.config.MinimumSeats}
|
||||
case err != nil:
|
||||
// we should never be here since Get returns either ObjectCountStaleErr or
|
||||
// ObjectCountNotFoundErr, return maximumSeats to be on the safe side.
|
||||
klog.ErrorS(err, "Unexpected error from object count tracker")
|
||||
return WorkEstimate{InitialSeats: e.config.MaximumSeats}
|
||||
}
|
||||
|
||||
limit := numStored
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking) && listOptions.Limit > 0 &&
|
||||
listOptions.Limit < numStored {
|
||||
limit = listOptions.Limit
|
||||
}
|
||||
|
||||
var estimatedObjectsToBeProcessed int64
|
||||
|
||||
switch {
|
||||
case isListFromCache:
|
||||
// TODO: For resources that implement indexes at the watchcache level,
|
||||
// we need to adjust the cost accordingly
|
||||
estimatedObjectsToBeProcessed = numStored
|
||||
case listOptions.FieldSelector != "" || listOptions.LabelSelector != "":
|
||||
estimatedObjectsToBeProcessed = numStored + limit
|
||||
default:
|
||||
estimatedObjectsToBeProcessed = 2 * limit
|
||||
}
|
||||
|
||||
// for now, our rough estimate is to allocate one seat to each 100 obejcts that
|
||||
// will be processed by the list request.
|
||||
// we will come up with a different formula for the transformation function and/or
|
||||
// fine tune this number in future iteratons.
|
||||
seats := uint64(math.Ceil(float64(estimatedObjectsToBeProcessed) / e.config.ObjectsPerSeat))
|
||||
|
||||
// make sure we never return a seat of zero
|
||||
if seats < e.config.MinimumSeats {
|
||||
seats = e.config.MinimumSeats
|
||||
}
|
||||
if seats > e.config.MaximumSeats {
|
||||
seats = e.config.MaximumSeats
|
||||
}
|
||||
return WorkEstimate{InitialSeats: seats}
|
||||
}
|
||||
|
||||
func key(requestInfo *apirequest.RequestInfo) string {
|
||||
groupResource := &schema.GroupResource{
|
||||
Group: requestInfo.APIGroup,
|
||||
Resource: requestInfo.Resource,
|
||||
}
|
||||
return groupResource.String()
|
||||
}
|
||||
|
||||
// NOTICE: Keep in sync with shouldDelegateList function in
|
||||
//
|
||||
// staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go
|
||||
func shouldListFromStorage(query url.Values, opts *metav1.ListOptions) bool {
|
||||
resourceVersion := opts.ResourceVersion
|
||||
pagingEnabled := utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
|
||||
hasContinuation := pagingEnabled && len(opts.Continue) > 0
|
||||
hasLimit := pagingEnabled && opts.Limit > 0 && resourceVersion != "0"
|
||||
return resourceVersion == "" || hasContinuation || hasLimit || opts.ResourceVersionMatch == metav1.ResourceVersionMatchExact
|
||||
}
|
149
vendor/k8s.io/apiserver/pkg/util/flowcontrol/request/mutating_work_estimator.go
generated
vendored
Normal file
149
vendor/k8s.io/apiserver/pkg/util/flowcontrol/request/mutating_work_estimator.go
generated
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
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 (
|
||||
"math"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/util/flowcontrol/metrics"
|
||||
)
|
||||
|
||||
func newMutatingWorkEstimator(countFn watchCountGetterFunc, config *WorkEstimatorConfig) WorkEstimatorFunc {
|
||||
estimator := &mutatingWorkEstimator{
|
||||
config: config,
|
||||
countFn: countFn,
|
||||
}
|
||||
return estimator.estimate
|
||||
}
|
||||
|
||||
type mutatingWorkEstimator struct {
|
||||
config *WorkEstimatorConfig
|
||||
countFn watchCountGetterFunc
|
||||
}
|
||||
|
||||
func (e *mutatingWorkEstimator) estimate(r *http.Request, flowSchemaName, priorityLevelName string) WorkEstimate {
|
||||
// TODO(wojtekt): Remove once we tune the algorithm to not fail
|
||||
// scalability tests.
|
||||
if !e.config.Enabled {
|
||||
return WorkEstimate{
|
||||
InitialSeats: 1,
|
||||
}
|
||||
}
|
||||
|
||||
requestInfo, ok := apirequest.RequestInfoFrom(r.Context())
|
||||
if !ok {
|
||||
// no RequestInfo should never happen, but to be on the safe side
|
||||
// let's return a large value.
|
||||
return WorkEstimate{
|
||||
InitialSeats: 1,
|
||||
FinalSeats: e.config.MaximumSeats,
|
||||
AdditionalLatency: e.config.eventAdditionalDuration(),
|
||||
}
|
||||
}
|
||||
|
||||
if isRequestExemptFromWatchEvents(requestInfo) {
|
||||
return WorkEstimate{
|
||||
InitialSeats: e.config.MinimumSeats,
|
||||
FinalSeats: 0,
|
||||
AdditionalLatency: time.Duration(0),
|
||||
}
|
||||
}
|
||||
|
||||
watchCount := e.countFn(requestInfo)
|
||||
metrics.ObserveWatchCount(r.Context(), priorityLevelName, flowSchemaName, watchCount)
|
||||
|
||||
// The cost of the request associated with the watchers of that event
|
||||
// consists of three parts:
|
||||
// - cost of going through the event change logic
|
||||
// - cost of serialization of the event
|
||||
// - cost of processing an event object for each watcher (e.g. filtering,
|
||||
// sending data over network)
|
||||
// We're starting simple to get some operational experience with it and
|
||||
// we will work on tuning the algorithm later. Given that the actual work
|
||||
// associated with processing watch events is happening in multiple
|
||||
// goroutines (proportional to the number of watchers) that are all
|
||||
// resumed at once, as a starting point we assume that each such goroutine
|
||||
// is taking 1/Nth of a seat for M milliseconds.
|
||||
// We allow the accounting of that work in P&F to be reshaped into another
|
||||
// rectangle of equal area for practical reasons.
|
||||
var finalSeats uint64
|
||||
var additionalLatency time.Duration
|
||||
|
||||
// TODO: Make this unconditional after we tune the algorithm better.
|
||||
// Technically, there is an overhead connected to processing an event after
|
||||
// the request finishes even if there is a small number of watches.
|
||||
// However, until we tune the estimation we want to stay on the safe side
|
||||
// an avoid introducing additional latency for almost every single request.
|
||||
if watchCount >= int(e.config.WatchesPerSeat) {
|
||||
// TODO: As described in the KEP, we should take into account that not all
|
||||
// events are equal and try to estimate the cost of a single event based on
|
||||
// some historical data about size of events.
|
||||
finalSeats = uint64(math.Ceil(float64(watchCount) / e.config.WatchesPerSeat))
|
||||
finalWork := SeatsTimesDuration(float64(finalSeats), e.config.eventAdditionalDuration())
|
||||
|
||||
// While processing individual events is highly parallel,
|
||||
// the design/implementation of P&F has a couple limitations that
|
||||
// make using this assumption in the P&F implementation very
|
||||
// inefficient because:
|
||||
// - we reserve max(initialSeats, finalSeats) for time of executing
|
||||
// both phases of the request
|
||||
// - even more importantly, when a given `wide` request is the one to
|
||||
// be dispatched, we are not dispatching any other request until
|
||||
// we accumulate enough seats to dispatch the nominated one, even
|
||||
// if currently unoccupied seats would allow for dispatching some
|
||||
// other requests in the meantime
|
||||
// As a consequence of these, the wider the request, the more capacity
|
||||
// will effectively be blocked and unused during dispatching and
|
||||
// executing this request.
|
||||
//
|
||||
// To mitigate the impact of it, we're capping the maximum number of
|
||||
// seats that can be assigned to a given request. Thanks to it:
|
||||
// 1) we reduce the amount of seat-seconds that are "wasted" during
|
||||
// dispatching and executing initial phase of the request
|
||||
// 2) we are not changing the finalWork estimate - just potentially
|
||||
// reshaping it to be narrower and longer. As long as the maximum
|
||||
// seats setting will prevent dispatching too many requests at once
|
||||
// to prevent overloading kube-apiserver (and/or etcd or the VM or
|
||||
// a physical machine it is running on), we believe the relaxed
|
||||
// version should be good enough to achieve the P&F goals.
|
||||
//
|
||||
// TODO: Confirm that the current cap of maximumSeats allow us to
|
||||
// achieve the above.
|
||||
if finalSeats > e.config.MaximumSeats {
|
||||
finalSeats = e.config.MaximumSeats
|
||||
}
|
||||
additionalLatency = finalWork.DurationPerSeat(float64(finalSeats))
|
||||
}
|
||||
|
||||
return WorkEstimate{
|
||||
InitialSeats: 1,
|
||||
FinalSeats: finalSeats,
|
||||
AdditionalLatency: additionalLatency,
|
||||
}
|
||||
}
|
||||
|
||||
func isRequestExemptFromWatchEvents(requestInfo *apirequest.RequestInfo) bool {
|
||||
// Creating token for service account does not produce any event,
|
||||
// but still serviceaccounts can have multiple watchers.
|
||||
if requestInfo.Resource == "serviceaccounts" && requestInfo.Subresource == "token" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
169
vendor/k8s.io/apiserver/pkg/util/flowcontrol/request/object_count_tracker.go
generated
vendored
Normal file
169
vendor/k8s.io/apiserver/pkg/util/flowcontrol/request/object_count_tracker.go
generated
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
const (
|
||||
// type deletion (it applies mostly to CRD) is not a very frequent
|
||||
// operation so we can afford to prune the cache at a large interval.
|
||||
// at the same time, we also want to make sure that the scalability
|
||||
// tests hit this code path.
|
||||
pruneInterval = 1 * time.Hour
|
||||
|
||||
// the storage layer polls for object count at every 1m interval, we will allow
|
||||
// up to 2-3 transient failures to get the latest count for a given resource.
|
||||
staleTolerationThreshold = 3 * time.Minute
|
||||
)
|
||||
|
||||
var (
|
||||
// ObjectCountNotFoundErr is returned when the object count for
|
||||
// a given resource is not being tracked.
|
||||
ObjectCountNotFoundErr = errors.New("object count not found for the given resource")
|
||||
|
||||
// ObjectCountStaleErr is returned when the object count for a
|
||||
// given resource has gone stale due to transient failures.
|
||||
ObjectCountStaleErr = errors.New("object count has gone stale for the given resource")
|
||||
)
|
||||
|
||||
// StorageObjectCountTracker is an interface that is used to keep track of
|
||||
// of the total number of objects for each resource.
|
||||
// {group}.{resource} is used as the key name to update and retrieve
|
||||
// the total number of objects for a given resource.
|
||||
type StorageObjectCountTracker interface {
|
||||
// Set is invoked to update the current number of total
|
||||
// objects for the given resource
|
||||
Set(string, int64)
|
||||
|
||||
// Get returns the total number of objects for the given resource.
|
||||
// The following errors are returned:
|
||||
// - if the count has gone stale for a given resource due to transient
|
||||
// failures ObjectCountStaleErr is returned.
|
||||
// - if the given resource is not being tracked then
|
||||
// ObjectCountNotFoundErr is returned.
|
||||
Get(string) (int64, error)
|
||||
|
||||
// RunUntil starts all the necessary maintenance.
|
||||
RunUntil(stopCh <-chan struct{})
|
||||
}
|
||||
|
||||
// NewStorageObjectCountTracker returns an instance of
|
||||
// StorageObjectCountTracker interface that can be used to
|
||||
// keep track of the total number of objects for each resource.
|
||||
func NewStorageObjectCountTracker() StorageObjectCountTracker {
|
||||
return &objectCountTracker{
|
||||
clock: &clock.RealClock{},
|
||||
counts: map[string]*timestampedCount{},
|
||||
}
|
||||
}
|
||||
|
||||
// timestampedCount stores the count of a given resource with a last updated
|
||||
// timestamp so we can prune it after it goes stale for certain threshold.
|
||||
type timestampedCount struct {
|
||||
count int64
|
||||
lastUpdatedAt time.Time
|
||||
}
|
||||
|
||||
// objectCountTracker implements StorageObjectCountTracker with
|
||||
// reader/writer mutual exclusion lock.
|
||||
type objectCountTracker struct {
|
||||
clock clock.PassiveClock
|
||||
|
||||
lock sync.RWMutex
|
||||
counts map[string]*timestampedCount
|
||||
}
|
||||
|
||||
func (t *objectCountTracker) Set(groupResource string, count int64) {
|
||||
if count <= -1 {
|
||||
// a value of -1 indicates that the 'Count' call failed to contact
|
||||
// the storage layer, in most cases this error can be transient.
|
||||
// we will continue to work with the count that is in the cache
|
||||
// up to a certain threshold defined by staleTolerationThreshold.
|
||||
// in case this becomes a non transient error then the count for
|
||||
// the given resource will will eventually be removed from
|
||||
// the cache by the pruner.
|
||||
return
|
||||
}
|
||||
|
||||
now := t.clock.Now()
|
||||
|
||||
// lock for writing
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if item, ok := t.counts[groupResource]; ok {
|
||||
item.count = count
|
||||
item.lastUpdatedAt = now
|
||||
return
|
||||
}
|
||||
|
||||
t.counts[groupResource] = ×tampedCount{
|
||||
count: count,
|
||||
lastUpdatedAt: now,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *objectCountTracker) Get(groupResource string) (int64, error) {
|
||||
staleThreshold := t.clock.Now().Add(-staleTolerationThreshold)
|
||||
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
if item, ok := t.counts[groupResource]; ok {
|
||||
if item.lastUpdatedAt.Before(staleThreshold) {
|
||||
return item.count, ObjectCountStaleErr
|
||||
}
|
||||
return item.count, nil
|
||||
}
|
||||
return 0, ObjectCountNotFoundErr
|
||||
}
|
||||
|
||||
// RunUntil runs all the necessary maintenance.
|
||||
func (t *objectCountTracker) RunUntil(stopCh <-chan struct{}) {
|
||||
wait.PollUntil(
|
||||
pruneInterval,
|
||||
func() (bool, error) {
|
||||
// always prune at every pruneInterval
|
||||
return false, t.prune(pruneInterval)
|
||||
}, stopCh)
|
||||
klog.InfoS("StorageObjectCountTracker pruner is exiting")
|
||||
}
|
||||
|
||||
func (t *objectCountTracker) prune(threshold time.Duration) error {
|
||||
oldestLastUpdatedAtAllowed := t.clock.Now().Add(-threshold)
|
||||
|
||||
// lock for writing
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
for groupResource, count := range t.counts {
|
||||
if count.lastUpdatedAt.After(oldestLastUpdatedAtAllowed) {
|
||||
continue
|
||||
}
|
||||
delete(t.counts, groupResource)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
65
vendor/k8s.io/apiserver/pkg/util/flowcontrol/request/seat_seconds.go
generated
vendored
Normal file
65
vendor/k8s.io/apiserver/pkg/util/flowcontrol/request/seat_seconds.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SeatSeconds is a measure of work, in units of seat-seconds, using a fixed-point representation.
|
||||
// `SeatSeconds(n)` represents `n/ssScale` seat-seconds.
|
||||
// The `ssScale` constant is private to the implementation here,
|
||||
// no other code should use it.
|
||||
type SeatSeconds uint64
|
||||
|
||||
// MaxSeatsSeconds is the maximum representable value of SeatSeconds
|
||||
const MaxSeatSeconds = SeatSeconds(math.MaxUint64)
|
||||
|
||||
// MinSeatSeconds is the lowest representable value of SeatSeconds
|
||||
const MinSeatSeconds = SeatSeconds(0)
|
||||
|
||||
// SeatsTimeDuration produces the SeatSeconds value for the given factors.
|
||||
// This is intended only to produce small values, increments in work
|
||||
// rather than amount of work done since process start.
|
||||
func SeatsTimesDuration(seats float64, duration time.Duration) SeatSeconds {
|
||||
return SeatSeconds(math.Round(seats * float64(duration/time.Nanosecond) / (1e9 / ssScale)))
|
||||
}
|
||||
|
||||
// ToFloat converts to a floating-point representation.
|
||||
// This conversion may lose precision.
|
||||
func (ss SeatSeconds) ToFloat() float64 {
|
||||
return float64(ss) / ssScale
|
||||
}
|
||||
|
||||
// DurationPerSeat returns duration per seat.
|
||||
// This division may lose precision.
|
||||
func (ss SeatSeconds) DurationPerSeat(seats float64) time.Duration {
|
||||
return time.Duration(float64(ss) / seats * (float64(time.Second) / ssScale))
|
||||
}
|
||||
|
||||
// String converts to a string.
|
||||
// This is suitable for large as well as small values.
|
||||
func (ss SeatSeconds) String() string {
|
||||
const div = SeatSeconds(ssScale)
|
||||
quo := ss / div
|
||||
rem := ss - quo*div
|
||||
return fmt.Sprintf("%d.%08dss", quo, rem)
|
||||
}
|
||||
|
||||
const ssScale = 1e8
|
113
vendor/k8s.io/apiserver/pkg/util/flowcontrol/request/width.go
generated
vendored
Normal file
113
vendor/k8s.io/apiserver/pkg/util/flowcontrol/request/width.go
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
apirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// WorkEstimate carries three of the four parameters that determine the work in a request.
|
||||
// The fourth parameter is the duration of the initial phase of execution.
|
||||
type WorkEstimate struct {
|
||||
// InitialSeats is the number of seats occupied while the server is
|
||||
// executing this request.
|
||||
InitialSeats uint64
|
||||
|
||||
// FinalSeats is the number of seats occupied at the end,
|
||||
// during the AdditionalLatency.
|
||||
FinalSeats uint64
|
||||
|
||||
// AdditionalLatency specifies the additional duration the seats allocated
|
||||
// to this request must be reserved after the given request had finished.
|
||||
// AdditionalLatency should not have any impact on the user experience, the
|
||||
// caller must not experience this additional latency.
|
||||
AdditionalLatency time.Duration
|
||||
}
|
||||
|
||||
// MaxSeats returns the maximum number of seats the request occupies over the
|
||||
// phases of being served.
|
||||
func (we *WorkEstimate) MaxSeats() int {
|
||||
if we.InitialSeats >= we.FinalSeats {
|
||||
return int(we.InitialSeats)
|
||||
}
|
||||
|
||||
return int(we.FinalSeats)
|
||||
}
|
||||
|
||||
// objectCountGetterFunc represents a function that gets the total
|
||||
// number of objects for a given resource.
|
||||
type objectCountGetterFunc func(string) (int64, error)
|
||||
|
||||
// watchCountGetterFunc represents a function that gets the total
|
||||
// number of watchers potentially interested in a given request.
|
||||
type watchCountGetterFunc func(*apirequest.RequestInfo) int
|
||||
|
||||
// NewWorkEstimator estimates the work that will be done by a given request,
|
||||
// if no WorkEstimatorFunc matches the given request then the default
|
||||
// work estimate of 1 seat is allocated to the request.
|
||||
func NewWorkEstimator(objectCountFn objectCountGetterFunc, watchCountFn watchCountGetterFunc, config *WorkEstimatorConfig) WorkEstimatorFunc {
|
||||
estimator := &workEstimator{
|
||||
minimumSeats: config.MinimumSeats,
|
||||
maximumSeats: config.MaximumSeats,
|
||||
listWorkEstimator: newListWorkEstimator(objectCountFn, config),
|
||||
mutatingWorkEstimator: newMutatingWorkEstimator(watchCountFn, config),
|
||||
}
|
||||
return estimator.estimate
|
||||
}
|
||||
|
||||
// WorkEstimatorFunc returns the estimated work of a given request.
|
||||
// This function will be used by the Priority & Fairness filter to
|
||||
// estimate the work of of incoming requests.
|
||||
type WorkEstimatorFunc func(request *http.Request, flowSchemaName, priorityLevelName string) WorkEstimate
|
||||
|
||||
func (e WorkEstimatorFunc) EstimateWork(r *http.Request, flowSchemaName, priorityLevelName string) WorkEstimate {
|
||||
return e(r, flowSchemaName, priorityLevelName)
|
||||
}
|
||||
|
||||
type workEstimator struct {
|
||||
// the minimum number of seats a request must occupy
|
||||
minimumSeats uint64
|
||||
// the maximum number of seats a request can occupy
|
||||
maximumSeats uint64
|
||||
// listWorkEstimator estimates work for list request(s)
|
||||
listWorkEstimator WorkEstimatorFunc
|
||||
// mutatingWorkEstimator calculates the width of mutating request(s)
|
||||
mutatingWorkEstimator WorkEstimatorFunc
|
||||
}
|
||||
|
||||
func (e *workEstimator) estimate(r *http.Request, flowSchemaName, priorityLevelName string) WorkEstimate {
|
||||
requestInfo, ok := apirequest.RequestInfoFrom(r.Context())
|
||||
if !ok {
|
||||
klog.ErrorS(fmt.Errorf("no RequestInfo found in context"), "Failed to estimate work for the request", "URI", r.RequestURI)
|
||||
// no RequestInfo should never happen, but to be on the safe side let's return maximumSeats
|
||||
return WorkEstimate{InitialSeats: e.maximumSeats}
|
||||
}
|
||||
|
||||
switch requestInfo.Verb {
|
||||
case "list":
|
||||
return e.listWorkEstimator.EstimateWork(r, flowSchemaName, priorityLevelName)
|
||||
case "create", "update", "patch", "delete":
|
||||
return e.mutatingWorkEstimator.EstimateWork(r, flowSchemaName, priorityLevelName)
|
||||
}
|
||||
|
||||
return WorkEstimate{InitialSeats: e.minimumSeats}
|
||||
}
|
Reference in New Issue
Block a user