// replenishQuota is a replenishment function invoked by a controller to notify that a quota should be recalculated func (rq *ResourceQuotaController) replenishQuota(groupKind unversioned.GroupKind, namespace string, object runtime.Object) { // check if the quota controller can evaluate this kind, if not, ignore it altogether... evaluators := rq.registry.Evaluators() evaluator, found := evaluators[groupKind] if !found { return } // check if this namespace even has a quota... indexKey := &api.ResourceQuota{} indexKey.Namespace = namespace resourceQuotas, err := rq.rqIndexer.Index("namespace", indexKey) if err != nil { glog.Errorf("quota controller could not find ResourceQuota associated with namespace: %s, could take up to %v before a quota replenishes", namespace, rq.resyncPeriod()) } if len(resourceQuotas) == 0 { return } // only queue those quotas that are tracking a resource associated with this kind. matchedResources := evaluator.MatchesResources() for i := range resourceQuotas { resourceQuota := resourceQuotas[i].(*api.ResourceQuota) resourceQuotaResources := quota.ResourceNames(resourceQuota.Status.Hard) if len(quota.Intersection(matchedResources, resourceQuotaResources)) > 0 { // TODO: make this support targeted replenishment to a specific kind, right now it does a full recalc on that quota. rq.enqueueResourceQuota(resourceQuota) } } }
// syncResourceQuota runs a complete sync of resource quota status across all known kinds func (rq *ResourceQuotaController) syncResourceQuota(resourceQuota api.ResourceQuota) (err error) { // quota is dirty if any part of spec hard limits differs from the status hard limits dirty := !api.Semantic.DeepEqual(resourceQuota.Spec.Hard, resourceQuota.Status.Hard) // dirty tracks if the usage status differs from the previous sync, // if so, we send a new usage with latest status // if this is our first sync, it will be dirty by default, since we need track usage dirty = dirty || (resourceQuota.Status.Hard == nil || resourceQuota.Status.Used == nil) // Create a usage object that is based on the quota resource version that will handle updates // by default, we preserve the past usage observation, and set hard to the current spec previousUsed := api.ResourceList{} if resourceQuota.Status.Used != nil { previousUsed = quota.Add(api.ResourceList{}, resourceQuota.Status.Used) } usage := api.ResourceQuota{ ObjectMeta: api.ObjectMeta{ Name: resourceQuota.Name, Namespace: resourceQuota.Namespace, ResourceVersion: resourceQuota.ResourceVersion, Labels: resourceQuota.Labels, Annotations: resourceQuota.Annotations}, Status: api.ResourceQuotaStatus{ Hard: quota.Add(api.ResourceList{}, resourceQuota.Spec.Hard), Used: previousUsed, }, } // find the intersection between the hard resources on the quota // and the resources this controller can track to know what we can // look to measure updated usage stats for hardResources := quota.ResourceNames(usage.Status.Hard) potentialResources := []api.ResourceName{} evaluators := rq.registry.Evaluators() for _, evaluator := range evaluators { potentialResources = append(potentialResources, evaluator.MatchesResources()...) } matchedResources := quota.Intersection(hardResources, potentialResources) // sum the observed usage from each evaluator newUsage := api.ResourceList{} usageStatsOptions := quota.UsageStatsOptions{Namespace: resourceQuota.Namespace, Scopes: resourceQuota.Spec.Scopes} for _, evaluator := range evaluators { stats, err := evaluator.UsageStats(usageStatsOptions) if err != nil { return err } newUsage = quota.Add(newUsage, stats.Used) } // mask the observed usage to only the set of resources tracked by this quota // merge our observed usage with the quota usage status // if the new usage is different than the last usage, we will need to do an update newUsage = quota.Mask(newUsage, matchedResources) for key, value := range newUsage { usage.Status.Used[key] = value } dirty = dirty || !quota.Equals(usage.Status.Used, resourceQuota.Status.Used) // there was a change observed by this controller that requires we update quota if dirty { _, err = rq.kubeClient.Core().ResourceQuotas(usage.Namespace).UpdateStatus(&usage) return err } return nil }