func (e *quotaEvaluator) Evaluate(a admission.Attributes) error { e.init.Do(func() { go e.run() }) // if we do not know how to evaluate use for this kind, just ignore evaluators := e.registry.Evaluators() evaluator, found := evaluators[a.GetKind().GroupKind()] if !found { return nil } // for this kind, check if the operation could mutate any quota resources // if no resources tracked by quota are impacted, then just return op := a.GetOperation() if !evaluator.Handles(op) { return nil } waiter := newAdmissionWaiter(a) e.addWork(waiter) // wait for completion or timeout select { case <-waiter.finished: case <-time.After(10 * time.Second): return fmt.Errorf("timeout") } return waiter.result }
// SupportsAttributes ignores all calls that do not deal with pod resources or storage requests (PVCs). // Also ignores any call that has a subresource defined. func (d *DefaultLimitRangerActions) SupportsAttributes(a admission.Attributes) bool { if a.GetSubresource() != "" { return false } return a.GetKind().GroupKind() == api.Kind("Pod") || a.GetKind().GroupKind() == api.Kind("PersistentVolumeClaim") }
func (p *provision) Admit(a admission.Attributes) (err error) { // if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do // if we're here, then the API server has found a route, which means that if we have a non-empty namespace // its a namespaced resource. if len(a.GetNamespace()) == 0 || a.GetKind().GroupKind() == api.Kind("Namespace") { return nil } // we need to wait for our caches to warm if !p.WaitForReady() { return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) } namespace := &api.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: a.GetNamespace(), Namespace: "", }, Status: api.NamespaceStatus{}, } _, exists, err := p.namespaceInformer.GetStore().Get(namespace) if err != nil { return admission.NewForbidden(a, err) } if exists { return nil } _, err = p.client.Core().Namespaces().Create(namespace) if err != nil && !errors.IsAlreadyExists(err) { return admission.NewForbidden(a, err) } return nil }
func (e *exists) Admit(a admission.Attributes) (err error) { // if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do // if we're here, then the API server has found a route, which means that if we have a non-empty namespace // its a namespaced resource. if len(a.GetNamespace()) == 0 || a.GetKind().GroupKind() == api.Kind("Namespace") { return nil } // we need to wait for our caches to warm if !e.WaitForReady() { return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) } namespace := &api.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: a.GetNamespace(), Namespace: "", }, Status: api.NamespaceStatus{}, } _, exists, err := e.namespaceInformer.GetStore().Get(namespace) if err != nil { return errors.NewInternalError(err) } if exists { return nil } // in case of latency in our caches, make a call direct to storage to verify that it truly exists or not _, err = e.client.Core().Namespaces().Get(a.GetNamespace(), metav1.GetOptions{}) if err != nil { if errors.IsNotFound(err) { return err } return errors.NewInternalError(err) } return nil }
// checkRequest verifies that the request does not exceed any quota constraint. it returns a copy of quotas not yet persisted // that capture what the usage would be if the request succeeded. It return an error if the is insufficient quota to satisfy the request func (e *quotaEvaluator) checkRequest(quotas []api.ResourceQuota, a admission.Attributes) ([]api.ResourceQuota, error) { namespace := a.GetNamespace() evaluators := e.registry.Evaluators() evaluator, found := evaluators[a.GetKind().GroupKind()] if !found { return quotas, nil } op := a.GetOperation() if !evaluator.Handles(op) { return quotas, nil } // find the set of quotas that are pertinent to this request // reject if we match the quota, but usage is not calculated yet // reject if the input object does not satisfy quota constraints // if there are no pertinent quotas, we can just return inputObject := a.GetObject() interestingQuotaIndexes := []int{} for i := range quotas { resourceQuota := quotas[i] match, err := evaluator.Matches(&resourceQuota, inputObject) if err != nil { return quotas, err } if !match { continue } hardResources := quota.ResourceNames(resourceQuota.Status.Hard) requiredResources := evaluator.MatchingResources(hardResources) if err := evaluator.Constraints(requiredResources, inputObject); err != nil { return nil, admission.NewForbidden(a, fmt.Errorf("failed quota: %s: %v", resourceQuota.Name, err)) } if !hasUsageStats(&resourceQuota) { return nil, admission.NewForbidden(a, fmt.Errorf("status unknown for quota: %s", resourceQuota.Name)) } interestingQuotaIndexes = append(interestingQuotaIndexes, i) } if len(interestingQuotaIndexes) == 0 { return quotas, nil } // Usage of some resources cannot be counted in isolation. For example, when // the resource represents a number of unique references to external // resource. In such a case an evaluator needs to process other objects in // the same namespace which needs to be known. if accessor, err := meta.Accessor(inputObject); namespace != "" && err == nil { if accessor.GetNamespace() == "" { accessor.SetNamespace(namespace) } } // there is at least one quota that definitely matches our object // as a result, we need to measure the usage of this object for quota // on updates, we need to subtract the previous measured usage // if usage shows no change, just return since it has no impact on quota deltaUsage, err := evaluator.Usage(inputObject) if err != nil { return quotas, err } // ensure that usage for input object is never negative (this would mean a resource made a negative resource requirement) if negativeUsage := quota.IsNegative(deltaUsage); len(negativeUsage) > 0 { return nil, admission.NewForbidden(a, fmt.Errorf("quota usage is negative for resource(s): %s", prettyPrintResourceNames(negativeUsage))) } if admission.Update == op { prevItem := a.GetOldObject() if prevItem == nil { return nil, admission.NewForbidden(a, fmt.Errorf("unable to get previous usage since prior version of object was not found")) } // if we can definitively determine that this is not a case of "create on update", // then charge based on the delta. Otherwise, bill the maximum metadata, err := meta.Accessor(prevItem) if err == nil && len(metadata.GetResourceVersion()) > 0 { prevUsage, innerErr := evaluator.Usage(prevItem) if innerErr != nil { return quotas, innerErr } deltaUsage = quota.Subtract(deltaUsage, prevUsage) } } if quota.IsZero(deltaUsage) { return quotas, nil } outQuotas, err := copyQuotas(quotas) if err != nil { return nil, err } for _, index := range interestingQuotaIndexes { resourceQuota := outQuotas[index] hardResources := quota.ResourceNames(resourceQuota.Status.Hard) requestedUsage := quota.Mask(deltaUsage, hardResources) newUsage := quota.Add(resourceQuota.Status.Used, requestedUsage) maskedNewUsage := quota.Mask(newUsage, quota.ResourceNames(requestedUsage)) if allowed, exceeded := quota.LessThanOrEqual(maskedNewUsage, resourceQuota.Status.Hard); !allowed { failedRequestedUsage := quota.Mask(requestedUsage, exceeded) failedUsed := quota.Mask(resourceQuota.Status.Used, exceeded) failedHard := quota.Mask(resourceQuota.Status.Hard, exceeded) return nil, admission.NewForbidden(a, fmt.Errorf("exceeded quota: %s, requested: %s, used: %s, limited: %s", resourceQuota.Name, prettyPrint(failedRequestedUsage), prettyPrint(failedUsed), prettyPrint(failedHard))) } // update to the new usage number outQuotas[index].Status.Used = newUsage } return outQuotas, nil }
// Admit enforces that pod and its namespace node label selectors matches at least a node in the cluster. func (p *podNodeSelector) Admit(a admission.Attributes) error { resource := a.GetResource().GroupResource() if resource != api.Resource("pods") { return nil } if a.GetSubresource() != "" { // only run the checks below on pods proper and not subresources return nil } obj := a.GetObject() pod, ok := obj.(*api.Pod) if !ok { glog.Errorf("expected pod but got %s", a.GetKind().Kind) return nil } if !p.WaitForReady() { return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) } name := pod.Name nsName := a.GetNamespace() var namespace *api.Namespace namespaceObj, exists, err := p.namespaceInformer.GetStore().Get(&api.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: nsName, Namespace: "", }, }) if err != nil { return errors.NewInternalError(err) } if exists { namespace = namespaceObj.(*api.Namespace) } else { namespace, err = p.defaultGetNamespace(nsName) if err != nil { if errors.IsNotFound(err) { return err } return errors.NewInternalError(err) } } namespaceNodeSelector, err := p.getNodeSelectorMap(namespace) if err != nil { return err } if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) { return errors.NewForbidden(resource, name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector")) } whitelist, err := labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors[namespace.Name]) if err != nil { return err } // Merge pod node selector = namespace node selector + current pod node selector podNodeSelectorLabels := labels.Merge(namespaceNodeSelector, pod.Spec.NodeSelector) // whitelist verification if !labels.AreLabelsInWhiteList(podNodeSelectorLabels, whitelist) { return errors.NewForbidden(resource, name, fmt.Errorf("pod node label selector labels conflict with its namespace whitelist")) } // Updated pod node selector = namespace node selector + current pod node selector pod.Spec.NodeSelector = map[string]string(podNodeSelectorLabels) return nil }
func (l *lifecycle) Admit(a admission.Attributes) error { // prevent deletion of immortal namespaces if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == api.Kind("Namespace") && l.immortalNamespaces.Has(a.GetName()) { return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted")) } // if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do // if we're here, then the API server has found a route, which means that if we have a non-empty namespace // its a namespaced resource. if len(a.GetNamespace()) == 0 || a.GetKind().GroupKind() == api.Kind("Namespace") { // if a namespace is deleted, we want to prevent all further creates into it // while it is undergoing termination. to reduce incidences where the cache // is slow to update, we add the namespace into a force live lookup list to ensure // we are not looking at stale state. if a.GetOperation() == admission.Delete { l.forceLiveLookupCache.Add(a.GetName(), true, forceLiveLookupTTL) } return nil } // we need to wait for our caches to warm if !l.WaitForReady() { return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) } var ( namespaceObj interface{} exists bool err error ) key := makeNamespaceKey(a.GetNamespace()) namespaceObj, exists, err = l.namespaceInformer.GetStore().Get(key) if err != nil { return errors.NewInternalError(err) } if !exists && a.GetOperation() == admission.Create { // give the cache time to observe the namespace before rejecting a create. // this helps when creating a namespace and immediately creating objects within it. time.Sleep(missingNamespaceWait) namespaceObj, exists, err = l.namespaceInformer.GetStore().Get(key) if err != nil { return errors.NewInternalError(err) } if exists { glog.V(4).Infof("found %s in cache after waiting", a.GetNamespace()) } } // forceLiveLookup if true will skip looking at local cache state and instead always make a live call to server. forceLiveLookup := false if _, ok := l.forceLiveLookupCache.Get(a.GetNamespace()); ok { // we think the namespace was marked for deletion, but our current local cache says otherwise, we will force a live lookup. forceLiveLookup = exists && namespaceObj.(*api.Namespace).Status.Phase == api.NamespaceActive } // refuse to operate on non-existent namespaces if !exists || forceLiveLookup { // as a last resort, make a call directly to storage namespaceObj, err = l.client.Core().Namespaces().Get(a.GetNamespace(), metav1.GetOptions{}) if err != nil { if errors.IsNotFound(err) { return err } return errors.NewInternalError(err) } glog.V(4).Infof("found %s via storage lookup", a.GetNamespace()) } // ensure that we're not trying to create objects in terminating namespaces if a.GetOperation() == admission.Create { namespace := namespaceObj.(*api.Namespace) if namespace.Status.Phase != api.NamespaceTerminating { return nil } // TODO: This should probably not be a 403 return admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated.", a.GetNamespace())) } return nil }