New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
use a separate queue for initial quota calculation #29133
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,6 +63,8 @@ type ResourceQuotaController struct { | |
rqController *framework.Controller | ||
// ResourceQuota objects that need to be synchronized | ||
queue workqueue.RateLimitingInterface | ||
// missingUsageQueue holds objects that are missing the initial usage informatino | ||
missingUsageQueue workqueue.RateLimitingInterface | ||
// To allow injection of syncUsage for testing. | ||
syncHandler func(key string) error | ||
// function that controls full recalculation of quota usage | ||
|
@@ -78,6 +80,7 @@ func NewResourceQuotaController(options *ResourceQuotaControllerOptions) *Resour | |
rq := &ResourceQuotaController{ | ||
kubeClient: options.KubeClient, | ||
queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()), | ||
missingUsageQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()), | ||
resyncPeriod: options.ResyncPeriod, | ||
registry: options.Registry, | ||
replenishmentControllers: []framework.ControllerInterface{}, | ||
|
@@ -101,7 +104,7 @@ func NewResourceQuotaController(options *ResourceQuotaControllerOptions) *Resour | |
&api.ResourceQuota{}, | ||
rq.resyncPeriod(), | ||
framework.ResourceEventHandlerFuncs{ | ||
AddFunc: rq.enqueueResourceQuota, | ||
AddFunc: rq.addQuota, | ||
UpdateFunc: func(old, cur interface{}) { | ||
// We are only interested in observing updates to quota.spec to drive updates to quota.status. | ||
// We ignore all updates to quota.Status because they are all driven by this controller. | ||
|
@@ -116,7 +119,7 @@ func NewResourceQuotaController(options *ResourceQuotaControllerOptions) *Resour | |
if quota.Equals(curResourceQuota.Spec.Hard, oldResourceQuota.Spec.Hard) { | ||
return | ||
} | ||
rq.enqueueResourceQuota(curResourceQuota) | ||
rq.addQuota(curResourceQuota) | ||
}, | ||
// This will enter the sync loop and no-op, because the controller has been deleted from the store. | ||
// Note that deleting a controller immediately after scaling it to 0 will not work. The recommended | ||
|
@@ -160,28 +163,63 @@ func (rq *ResourceQuotaController) enqueueResourceQuota(obj interface{}) { | |
rq.queue.Add(key) | ||
} | ||
|
||
func (rq *ResourceQuotaController) addQuota(obj interface{}) { | ||
key, err := controller.KeyFunc(obj) | ||
if err != nil { | ||
glog.Errorf("Couldn't get key for object %+v: %v", obj, err) | ||
return | ||
} | ||
|
||
resourceQuota := obj.(*api.ResourceQuota) | ||
|
||
// if we declared an intent that is not yet captured in status (prioritize it) | ||
if !api.Semantic.DeepEqual(resourceQuota.Spec.Hard, resourceQuota.Status.Hard) { | ||
rq.missingUsageQueue.Add(key) | ||
return | ||
} | ||
|
||
// if we declared a constraint that has no usage (which this controller can calculate, prioritize it) | ||
for constraint := range resourceQuota.Status.Hard { | ||
if _, usageFound := resourceQuota.Status.Used[constraint]; !usageFound { | ||
matchedResources := []api.ResourceName{constraint} | ||
|
||
for _, evaluator := range rq.registry.Evaluators() { | ||
if intersection := quota.Intersection(evaluator.MatchesResources(), matchedResources); len(intersection) != 0 { | ||
rq.missingUsageQueue.Add(key) | ||
return | ||
} | ||
} | ||
} | ||
} | ||
|
||
// no special priority, go in normal recalc queue | ||
rq.queue.Add(key) | ||
} | ||
|
||
// worker runs a worker thread that just dequeues items, processes them, and marks them done. | ||
// It enforces that the syncHandler is never invoked concurrently with the same key. | ||
func (rq *ResourceQuotaController) worker() { | ||
func (rq *ResourceQuotaController) worker(queue workqueue.RateLimitingInterface) func() { | ||
workFunc := func() bool { | ||
key, quit := rq.queue.Get() | ||
key, quit := queue.Get() | ||
if quit { | ||
return true | ||
} | ||
defer rq.queue.Done(key) | ||
defer queue.Done(key) | ||
err := rq.syncHandler(key.(string)) | ||
if err == nil { | ||
rq.queue.Forget(key) | ||
queue.Forget(key) | ||
return false | ||
} | ||
utilruntime.HandleError(err) | ||
rq.queue.AddRateLimited(key) | ||
queue.AddRateLimited(key) | ||
return false | ||
} | ||
for { | ||
if quit := workFunc(); quit { | ||
glog.Infof("resource quota controller worker shutting down") | ||
return | ||
|
||
return func() { | ||
for { | ||
if quit := workFunc(); quit { | ||
glog.Infof("resource quota controller worker shutting down") | ||
return | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -196,7 +234,8 @@ func (rq *ResourceQuotaController) Run(workers int, stopCh <-chan struct{}) { | |
} | ||
// the workers that chug through the quota calculation backlog | ||
for i := 0; i < workers; i++ { | ||
go wait.Until(rq.worker, time.Second, stopCh) | ||
go wait.Until(rq.worker(rq.queue), time.Second, stopCh) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this break some external contract? should a worker first look at the priority queue and if empty then fall back to the normal queue? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't think so. It's not a strict "only start this number of threads", it's more a "only do this amount of threadiness". There's no clean way to do what you've suggested with a work queue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agreed |
||
go wait.Until(rq.worker(rq.missingUsageQueue), time.Second, stopCh) | ||
} | ||
// the timer for how often we do a full recalculation across all quotas | ||
go wait.Until(func() { rq.enqueueAll() }, rq.resyncPeriod(), stopCh) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: information