feat: add globalresourcequota api (#2068)

* feat: add globalresourcequota api

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
This commit is contained in:
Oliver Bähler
2026-08-10 21:25:10 +02:00
committed by GitHub
parent d92453427c
commit bdcdcefe63
60 changed files with 6804 additions and 107 deletions
+64
View File
@@ -0,0 +1,64 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
"crypto/sha256"
"fmt"
"sort"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
runtimequota "github.com/projectcapsule/capsule/pkg/runtime/quota"
)
func (q *GlobalResourceQuota) GetResourceQuotaName() string {
sum := sha256.Sum256([]byte(q.Name))
return fmt.Sprintf("capsule-global-quota-%x", sum[:10])
}
func (q *GlobalResourceQuota) GetLedgerName() string {
sum := sha256.Sum256(fmt.Appendf(nil, "%s/%s", q.Name, q.UID))
return fmt.Sprintf("global-resource-quota-%x", sum[:16])
}
func (q *GlobalResourceQuota) AssignNamespaces(namespaces []corev1.Namespace) {
names := make([]string, 0, len(namespaces))
for i := range namespaces {
ns := &namespaces[i]
if ns.Status.Phase == corev1.NamespaceActive && ns.DeletionTimestamp == nil {
names = append(names, ns.Name)
}
}
sort.Strings(names)
q.Status.Namespaces = names
q.Status.NamespaceSize = uint(len(names))
}
func (q *GlobalResourceQuota) CalculateAvailable() {
available := make(corev1.ResourceList, len(q.Status.Total.Hard))
for name, hard := range q.Status.Total.Hard {
value := hard.DeepCopy()
value.Sub(q.Status.Total.Used[name])
runtimequota.ClampQuantityToZero(&value)
available[name] = value
}
q.Status.Total.Available = available
}
func ZeroResourceList(resources corev1.ResourceList) corev1.ResourceList {
out := make(corev1.ResourceList, len(resources))
for name := range resources {
out[name] = *resource.NewQuantity(0, resource.DecimalSI)
}
return out
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
corev1 "k8s.io/api/core/v1"
"github.com/projectcapsule/capsule/pkg/api/meta"
)
type GlobalResourceQuotaStatus struct {
// ObservedGeneration is the most recent generation observed by the controller.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// NamespaceSize is the number of selected namespaces.
// +kubebuilder:default=0
NamespaceSize uint `json:"namespaceCount,omitempty"`
// Namespaces is the ordered set of selected namespace names.
Namespaces []string `json:"namespaces,omitempty"`
// Total contains aggregate quota usage across all selected namespaces.
Total GlobalResourceQuotaUsage `json:"total,omitzero"`
// NamespaceUsage contains observed quota usage per selected namespace.
NamespaceUsage GlobalResourceQuotaNamespaceUsage `json:"namespaceUsage,omitempty"`
// Conditions report reconciliation and admission readiness.
Conditions meta.ConditionList `json:"conditions,omitzero"`
}
type GlobalResourceQuotaUsage struct {
// Hard is the configured shared limit.
Hard corev1.ResourceList `json:"hard,omitempty"`
// Used is the usage observed across the relevant namespace set.
Used corev1.ResourceList `json:"used,omitempty"`
// Available is max(Hard-Used, 0).
Available corev1.ResourceList `json:"available,omitempty"`
}
type GlobalResourceQuotaNamespaceUsage map[string]GlobalResourceQuotaNamespaceStatus
type GlobalResourceQuotaNamespaceStatus struct {
// Used is the usage observed in this namespace.
Used corev1.ResourceList `json:"used,omitempty"`
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/projectcapsule/capsule/pkg/runtime/selectors"
)
// GlobalResourceQuotaSpec defines a native ResourceQuota shared by every
// namespace matched by any namespace selector.
type GlobalResourceQuotaSpec struct {
// NamespaceSelectors select the namespaces that share this quota.
// Selectors are ORed; requirements within one selector are ANDed. An empty
// label selector matches all namespaces.
NamespaceSelectors []selectors.NamespaceSelector `json:"namespaceSelectors,omitempty"`
// Quota is the native Kubernetes ResourceQuota specification enforced
// across the selected namespaces.
Quota corev1.ResourceQuotaSpec `json:"quota"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster,shortName=globalquota;grq
// +kubebuilder:printcolumn:name="Namespaces",type="integer",JSONPath=".status.namespaceCount",description="Selected namespaces"
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="Reconcile status"
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description="Reconcile message"
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=".metadata.creationTimestamp"
type GlobalResourceQuota struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ObjectMeta `json:"metadata,omitzero"`
Spec GlobalResourceQuotaSpec `json:"spec"`
// +optional
Status GlobalResourceQuotaStatus `json:"status,omitzero"`
}
// +kubebuilder:object:root=true
type GlobalResourceQuotaList struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ListMeta `json:"metadata,omitzero"`
Items []GlobalResourceQuota `json:"items"`
}
+2
View File
@@ -31,6 +31,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&CustomQuotaList{},
&GlobalCustomQuota{},
&GlobalCustomQuotaList{},
&GlobalResourceQuota{},
&GlobalResourceQuotaList{},
&GlobalTenantResource{},
&GlobalTenantResourceList{},
&QuantityLedger{},
+55
View File
@@ -4,6 +4,7 @@
package v1beta2
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -57,6 +58,55 @@ type QuantityLedgerPendingDelete struct {
CreatedAt metav1.Time `json:"createdAt"`
}
// QuantityLedgerResourceQuotaReservation is an atomic reservation against all
// resources tracked by a GlobalResourceQuota.
type QuantityLedgerResourceQuotaReservation struct {
// Unique reservation identifier.
// +kubebuilder:validation:MinLength=1
ID string `json:"id"`
// Usage is the calculated usage of the admitted object.
Usage corev1.ResourceList `json:"usage,omitempty"`
// Delta is the positive amount held while ResourceQuota status catches up.
Delta corev1.ResourceList `json:"delta,omitempty"`
// Object that this reservation is intended to create/update.
ObjectRef QuantityLedgerObjectRef `json:"objectRef"`
CreatedAt metav1.Time `json:"createdAt"`
UpdatedAt metav1.Time `json:"updatedAt"`
ExpiresAt *metav1.Time `json:"expiresAt,omitempty"`
}
// QuantityLedgerResourceQuotaStatus is the coordination state for one
// GlobalResourceQuota.
type QuantityLedgerResourceQuotaStatus struct {
// ObservedGeneration is the GlobalResourceQuota generation represented by
// this ledger state.
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Initialized is true after every selected namespace has reported
// ResourceQuota status for this quota.
Initialized bool `json:"initialized,omitempty"`
// Namespaces is the selected namespace set represented by Used.
Namespaces []string `json:"namespaces,omitempty"`
// Used is the usage observed from ResourceQuota status across all selected
// namespaces.
Used corev1.ResourceList `json:"used,omitempty"`
// Reserved is derived from Reservations.
Reserved corev1.ResourceList `json:"reserved,omitempty"`
// Allocated is Used plus all active reservations.
Allocated corev1.ResourceList `json:"allocated,omitempty"`
// Reservations contains inflight admission operations.
Reservations []QuantityLedgerResourceQuotaReservation `json:"reservations,omitempty"`
}
// QuantityLedgerStatus contains the mutable coordination state used by admission
// and quota controllers.
type QuantityLedgerStatus struct {
@@ -80,4 +130,9 @@ type QuantityLedgerStatus struct {
// Allocated is the admission-owned total that has been accepted by the webhook.
// It must be updated only through optimistic concurrency on QuantityLedger.
Allocated resource.Quantity `json:"allocated,omitempty"`
// ResourceQuota contains coordination state for GlobalResourceQuota.
// It is unset for CustomQuota and GlobalCustomQuota ledgers.
// +optional
ResourceQuota *QuantityLedgerResourceQuotaStatus `json:"resourceQuota,omitempty"`
}
+285
View File
@@ -656,6 +656,202 @@ func (in *GlobalCustomQuotaStatus) DeepCopy() *GlobalCustomQuotaStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GlobalResourceQuota) DeepCopyInto(out *GlobalResourceQuota) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalResourceQuota.
func (in *GlobalResourceQuota) DeepCopy() *GlobalResourceQuota {
if in == nil {
return nil
}
out := new(GlobalResourceQuota)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *GlobalResourceQuota) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GlobalResourceQuotaList) DeepCopyInto(out *GlobalResourceQuotaList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]GlobalResourceQuota, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalResourceQuotaList.
func (in *GlobalResourceQuotaList) DeepCopy() *GlobalResourceQuotaList {
if in == nil {
return nil
}
out := new(GlobalResourceQuotaList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *GlobalResourceQuotaList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GlobalResourceQuotaNamespaceStatus) DeepCopyInto(out *GlobalResourceQuotaNamespaceStatus) {
*out = *in
if in.Used != nil {
in, out := &in.Used, &out.Used
*out = make(corev1.ResourceList, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalResourceQuotaNamespaceStatus.
func (in *GlobalResourceQuotaNamespaceStatus) DeepCopy() *GlobalResourceQuotaNamespaceStatus {
if in == nil {
return nil
}
out := new(GlobalResourceQuotaNamespaceStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in GlobalResourceQuotaNamespaceUsage) DeepCopyInto(out *GlobalResourceQuotaNamespaceUsage) {
{
in := &in
*out = make(GlobalResourceQuotaNamespaceUsage, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalResourceQuotaNamespaceUsage.
func (in GlobalResourceQuotaNamespaceUsage) DeepCopy() GlobalResourceQuotaNamespaceUsage {
if in == nil {
return nil
}
out := new(GlobalResourceQuotaNamespaceUsage)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GlobalResourceQuotaSpec) DeepCopyInto(out *GlobalResourceQuotaSpec) {
*out = *in
if in.NamespaceSelectors != nil {
in, out := &in.NamespaceSelectors, &out.NamespaceSelectors
*out = make([]selectors.NamespaceSelector, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.Quota.DeepCopyInto(&out.Quota)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalResourceQuotaSpec.
func (in *GlobalResourceQuotaSpec) DeepCopy() *GlobalResourceQuotaSpec {
if in == nil {
return nil
}
out := new(GlobalResourceQuotaSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GlobalResourceQuotaStatus) DeepCopyInto(out *GlobalResourceQuotaStatus) {
*out = *in
if in.Namespaces != nil {
in, out := &in.Namespaces, &out.Namespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
in.Total.DeepCopyInto(&out.Total)
if in.NamespaceUsage != nil {
in, out := &in.NamespaceUsage, &out.NamespaceUsage
*out = make(GlobalResourceQuotaNamespaceUsage, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make(meta.ConditionList, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalResourceQuotaStatus.
func (in *GlobalResourceQuotaStatus) DeepCopy() *GlobalResourceQuotaStatus {
if in == nil {
return nil
}
out := new(GlobalResourceQuotaStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GlobalResourceQuotaUsage) DeepCopyInto(out *GlobalResourceQuotaUsage) {
*out = *in
if in.Hard != nil {
in, out := &in.Hard, &out.Hard
*out = make(corev1.ResourceList, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
if in.Used != nil {
in, out := &in.Used, &out.Used
*out = make(corev1.ResourceList, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
if in.Available != nil {
in, out := &in.Available, &out.Available
*out = make(corev1.ResourceList, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalResourceQuotaUsage.
func (in *GlobalResourceQuotaUsage) DeepCopy() *GlobalResourceQuotaUsage {
if in == nil {
return nil
}
out := new(GlobalResourceQuotaUsage)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GlobalTenantResource) DeepCopyInto(out *GlobalTenantResource) {
*out = *in
@@ -999,6 +1195,90 @@ func (in *QuantityLedgerReservation) DeepCopy() *QuantityLedgerReservation {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *QuantityLedgerResourceQuotaReservation) DeepCopyInto(out *QuantityLedgerResourceQuotaReservation) {
*out = *in
if in.Usage != nil {
in, out := &in.Usage, &out.Usage
*out = make(corev1.ResourceList, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
if in.Delta != nil {
in, out := &in.Delta, &out.Delta
*out = make(corev1.ResourceList, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
out.ObjectRef = in.ObjectRef
in.CreatedAt.DeepCopyInto(&out.CreatedAt)
in.UpdatedAt.DeepCopyInto(&out.UpdatedAt)
if in.ExpiresAt != nil {
in, out := &in.ExpiresAt, &out.ExpiresAt
*out = (*in).DeepCopy()
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuantityLedgerResourceQuotaReservation.
func (in *QuantityLedgerResourceQuotaReservation) DeepCopy() *QuantityLedgerResourceQuotaReservation {
if in == nil {
return nil
}
out := new(QuantityLedgerResourceQuotaReservation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *QuantityLedgerResourceQuotaStatus) DeepCopyInto(out *QuantityLedgerResourceQuotaStatus) {
*out = *in
if in.Namespaces != nil {
in, out := &in.Namespaces, &out.Namespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Used != nil {
in, out := &in.Used, &out.Used
*out = make(corev1.ResourceList, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
if in.Reserved != nil {
in, out := &in.Reserved, &out.Reserved
*out = make(corev1.ResourceList, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
if in.Allocated != nil {
in, out := &in.Allocated, &out.Allocated
*out = make(corev1.ResourceList, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
if in.Reservations != nil {
in, out := &in.Reservations, &out.Reservations
*out = make([]QuantityLedgerResourceQuotaReservation, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuantityLedgerResourceQuotaStatus.
func (in *QuantityLedgerResourceQuotaStatus) DeepCopy() *QuantityLedgerResourceQuotaStatus {
if in == nil {
return nil
}
out := new(QuantityLedgerResourceQuotaStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *QuantityLedgerSpec) DeepCopyInto(out *QuantityLedgerSpec) {
*out = *in
@@ -1041,6 +1321,11 @@ func (in *QuantityLedgerStatus) DeepCopyInto(out *QuantityLedgerStatus) {
}
}
out.Allocated = in.Allocated.DeepCopy()
if in.ResourceQuota != nil {
in, out := &in.ResourceQuota, &out.ResourceQuota
*out = new(QuantityLedgerResourceQuotaStatus)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuantityLedgerStatus.