diff --git a/artifacts/resourcequota/ns-quota.yaml b/artifacts/resourcequota/ns-quota.yaml new file mode 100644 index 0000000000..9cb9079cc4 --- /dev/null +++ b/artifacts/resourcequota/ns-quota.yaml @@ -0,0 +1,13 @@ +apiVersion: management.k8s.appscode.com/v1alpha1 +kind: ProjectQuota +metadata: + name: demo +spec: + quotas: + - group: kubedb.com + kind: Postgres + hard: + limits.memory: 1Gi + - group: kubedb.com + hard: + limits.memory: 1Gi diff --git a/artifacts/resourcequota/quota.yaml b/artifacts/resourcequota/project-quota.yaml similarity index 100% rename from artifacts/resourcequota/quota.yaml rename to artifacts/resourcequota/project-quota.yaml diff --git a/go.mod b/go.mod index 579fd275a4..caa92bacc7 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( k8s.io/kube-state-metrics/v2 v2.12.0 kmodules.xyz/apiversion v0.2.0 kmodules.xyz/authorizer v0.29.1 - kmodules.xyz/client-go v0.30.13-0.20240820121523-30dbfd82b4c6 + kmodules.xyz/client-go v0.30.13 kmodules.xyz/custom-resources v0.30.0 kmodules.xyz/go-containerregistry v0.0.12 kmodules.xyz/monitoring-agent-api v0.29.0 diff --git a/go.sum b/go.sum index a8a0a800c8..ef341d952e 100644 --- a/go.sum +++ b/go.sum @@ -908,8 +908,8 @@ kmodules.xyz/apiversion v0.2.0 h1:vAQYqZFm4xu4pbB1cAdHbFEPES6EQkcR4wc06xdTOWk= kmodules.xyz/apiversion v0.2.0/go.mod h1:oPX8g8LvlPdPX3Yc5YvCzJHQnw3YF/X4/jdW0b1am80= kmodules.xyz/authorizer v0.29.1 h1:uByGGoryKbZcfiEAhjcK/Y345I9mygNQP7DVpkMbNQQ= kmodules.xyz/authorizer v0.29.1/go.mod h1:kZRhclL8twzyt2bQuJQJbpYww2sc+qFr8I5PPoq/sWY= -kmodules.xyz/client-go v0.30.13-0.20240820121523-30dbfd82b4c6 h1:WsCrZxH8FD5WEiiHdFwXrjnURyUnQSLA7fa3VqzVu38= -kmodules.xyz/client-go v0.30.13-0.20240820121523-30dbfd82b4c6/go.mod h1:XL3PDQIXG4s3xNRL2SSxIvi8b2WyMGpn26dFnOBz0j4= +kmodules.xyz/client-go v0.30.13 h1:Tpvg7ZlSNlzjB0NZT9hUe9JrO7FN4PjUUF1hNUXSku0= +kmodules.xyz/client-go v0.30.13/go.mod h1:XL3PDQIXG4s3xNRL2SSxIvi8b2WyMGpn26dFnOBz0j4= kmodules.xyz/crd-schema-fuzz v0.29.1 h1:zJTlWYOrT5dsVVHW8HGcnR/vaWfxQfNh11QwTtkYpcs= kmodules.xyz/crd-schema-fuzz v0.29.1/go.mod h1:n708z9YQqLMP2KNLQVgBcRJw1QpSWLvpNCEi+KJDOYE= kmodules.xyz/custom-resources v0.30.0 h1:vR3CbseHMLwR4GvtcJJuRuwIV8voKqFqNii27rMcm1o= diff --git a/pkg/controllers/projectquota/projectquota_controller.go b/pkg/controllers/projectquota/projectquota_controller.go index 2fca0b321e..0767fcef4d 100644 --- a/pkg/controllers/projectquota/projectquota_controller.go +++ b/pkg/controllers/projectquota/projectquota_controller.go @@ -73,7 +73,6 @@ func (r *ProjectQuotaReconciler) Reconcile(ctx context.Context, req ctrl.Request var pj v1alpha1.ProjectQuota if err := r.Get(ctx, req.NamespacedName, &pj); err != nil { - log.Error(err, "unable to fetch ProjectQuota") return ctrl.Result{}, client.IgnoreNotFound(err) } @@ -164,10 +163,7 @@ type QuotaResult struct { } func (r *ProjectQuotaReconciler) CalculateStatus(pj *v1alpha1.ProjectQuota) error { - var nsList core.NamespaceList - err := r.List(context.TODO(), &nsList, client.MatchingLabels{ - clustermeta.LabelKeyRancherFieldProjectId: pj.Name, - }) + nsList, err := r.getNamespaceList(pj.Name) if err != nil { return err } @@ -266,6 +262,29 @@ func (r *ProjectQuotaReconciler) CalculateStatus(pj *v1alpha1.ProjectQuota) erro return nil } +func (r *ProjectQuotaReconciler) getNamespaceList(name string) (*core.NamespaceList, error) { + var nsList core.NamespaceList + if clustermeta.IsRancherManaged(r.RESTMapper()) { + err := r.List(context.TODO(), &nsList, client.MatchingLabels{ + clustermeta.LabelKeyRancherFieldProjectId: name, + }) + if err != nil { + return nil, err + } + if len(nsList.Items) > 0 { + return &nsList, nil + } + } + + err := r.List(context.TODO(), &nsList, client.MatchingLabels{ + core.LabelMetadataName: name, + }) + if err != nil { + return nil, err + } + return &nsList, nil +} + func (r *ProjectQuotaReconciler) UsedQuota(ns string, typeInfo APIType) (core.ResourceList, *QuotaResult, error) { gk := schema.GroupKind{ Group: typeInfo.Group, @@ -394,12 +413,19 @@ func ProjectQuotaForObjects(kc client.Client) func(_ context.Context, _ client.O return nil } - projectId, found := ns.Labels[clustermeta.LabelKeyRancherFieldProjectId] - if !found { - return nil - } - return []reconcile.Request{ - {NamespacedName: types.NamespacedName{Name: projectId}}, + var req []reconcile.Request + + if clustermeta.IsRancherManaged(kc.RESTMapper()) { + projectId, found := ns.Labels[clustermeta.LabelKeyRancherFieldProjectId] + if found { + req = append(req, reconcile.Request{ + NamespacedName: types.NamespacedName{Name: projectId}, + }) + } } + req = append(req, reconcile.Request{ + NamespacedName: types.NamespacedName{Name: ns.Name}, + }) + return req } } diff --git a/pkg/registry/meta/resourcecalculator/helpers.go b/pkg/registry/meta/resourcecalculator/helpers.go index 453aab3769..df3d1b3b06 100644 --- a/pkg/registry/meta/resourcecalculator/helpers.go +++ b/pkg/registry/meta/resourcecalculator/helpers.go @@ -21,7 +21,6 @@ import ( "strings" core "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" @@ -101,20 +100,28 @@ func mergeRequestsLimits(requests, limits core.ResourceList) core.ResourceList { return rl } -func getProjectQuota(kc client.Client, ns string) (*v1alpha1.ProjectQuota, error) { - projectId, _, err := clustermeta.GetProjectId(kc, ns) - if err != nil { - return nil, err +func getApplicableQuotas(kc client.Client, ns string) ([]*v1alpha1.ProjectQuota, error) { + var names []string + if clustermeta.IsRancherManaged(kc.RESTMapper()) { + projectId, _, err := clustermeta.GetProjectId(kc, ns) + if err != nil { + return nil, err + } + names = append(names, projectId) } - var pj v1alpha1.ProjectQuota - err = kc.Get(context.TODO(), client.ObjectKey{Name: projectId}, &pj) - if err != nil { - if apierrors.IsNotFound(err) { - return nil, nil + names = append(names, ns) + + var result []*v1alpha1.ProjectQuota + for _, name := range names { + var pj v1alpha1.ProjectQuota + err := kc.Get(context.TODO(), client.ObjectKey{Name: name}, &pj) + if client.IgnoreNotFound(err) != nil { + return nil, err + } else if err == nil { + result = append(result, &pj) } - return nil, err } - return &pj, nil + return result, nil } func getGVK(obj map[string]interface{}) schema.GroupVersionKind { diff --git a/pkg/registry/meta/resourcecalculator/storage.go b/pkg/registry/meta/resourcecalculator/storage.go index eebe1708d6..baba93e679 100644 --- a/pkg/registry/meta/resourcecalculator/storage.go +++ b/pkg/registry/meta/resourcecalculator/storage.go @@ -108,26 +108,40 @@ func (r *Storage) Create(ctx context.Context, obj runtime.Object, createValidati } rid := kmapi.NewResourceID(mapping) - pq, err := getProjectQuota(r.kc, u.GetNamespace()) - if err != nil { - return nil, err - } - // Wrap referenced db resource with the OpsRequest object if rid.Group == "ops.kubedb.com" { if err = utils.ExpandReferencedAppInOpsObject(r.kc, &u); err != nil { return nil, err } - } else if in.Request.Edit && pq != nil { - if err := deductOldDbObjectResourceUsageFromProjectQuota(r.kc, u, pq); err != nil { - return nil, err - } } - resp, err := ToGenericResource(&u, rid, pq) + resp, err := ToGenericResource(&u, rid) if err != nil { return nil, apierrors.NewInternalError(err) } + quotas, err := getApplicableQuotas(r.kc, u.GetNamespace()) + if err != nil { + return nil, err + } + // Wrap referenced db resource with the OpsRequest object + for i := range quotas { + pq := quotas[i] + if rid.Group != "ops.kubedb.com" && in.Request.Edit { + if err := deductOldDbObjectResourceUsageFromProjectQuota(r.kc, u, pq); err != nil { + return nil, err + } + } + + rv, err := quota(u.UnstructuredContent(), pq) + if err != nil { + return nil, err + } + resp.Quota = *rv + if rv.Decision == rsapi.DecisionDeny { + break + } + } + in.Response = resp return in, nil } @@ -136,7 +150,7 @@ func (r *Storage) ConvertToTable(ctx context.Context, object runtime.Object, tab return r.convertor.ConvertToTable(ctx, object, tableOptions) } -func ToGenericResource(item *unstructured.Unstructured, apiType *kmapi.ResourceID, pq *v1alpha1.ProjectQuota) (*rsapi.ResourceCalculatorResponse, error) { +func ToGenericResource(item *unstructured.Unstructured, apiType *kmapi.ResourceID) (*rsapi.ResourceCalculatorResponse, error) { content := item.UnstructuredContent() var genres rsapi.ResourceCalculatorResponse @@ -210,13 +224,6 @@ func ToGenericResource(item *unstructured.Unstructured, apiType *kmapi.ResourceI } genres.RoleResourceLimits = rv } - { - rv, err := quota(content, pq) - if err != nil { - return nil, err - } - genres.Quota = *rv - } } return &genres, nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 961217960c..e373328195 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1951,7 +1951,7 @@ kmodules.xyz/authorizer/apiserver kmodules.xyz/authorizer/rbac kmodules.xyz/authorizer/rbac/helpers kmodules.xyz/authorizer/rbac/validation -# kmodules.xyz/client-go v0.30.13-0.20240820121523-30dbfd82b4c6 +# kmodules.xyz/client-go v0.30.13 ## explicit; go 1.22.0 kmodules.xyz/client-go kmodules.xyz/client-go/api/v1