diff --git a/api/v1alpha2/hierarchicalresourcequota_types.go b/api/v1alpha2/hierarchicalresourcequota_types.go index a2d0dea3c..5eb267646 100644 --- a/api/v1alpha2/hierarchicalresourcequota_types.go +++ b/api/v1alpha2/hierarchicalresourcequota_types.go @@ -38,10 +38,20 @@ type HierarchicalResourceQuotaStatus struct { // and its descendant namespaces. // +optional Used corev1.ResourceList `json:"used,omitempty"` + // RequestsSummary is used by kubectl get hrq, and summarizes the relevant information + // from .status.hard.requests and .status.used.requests. + // +optional + RequestsSummary string `json:"requestsSummary,omitempty"` + // LimitsSummary is used by kubectl get hrq, and summarizes the relevant information + // from .status.hard.limits and .status.used.limits. + // +optional + LimitsSummary string `json:"limitsSummary,omitempty"` } // +kubebuilder:object:root=true // +kubebuilder:resource:path=hierarchicalresourcequotas,shortName=hrq,scope=Namespaced +// +kubebuilder:printcolumn:name="Request",type="string",JSONPath=".status.requestsSummary" +// +kubebuilder:printcolumn:name="Limit",type="string",JSONPath=".status.limitsSummary" // HierarchicalResourceQuota sets aggregate quota restrictions enforced for a // namespace and descendant namespaces diff --git a/config/crd/bases/hnc.x-k8s.io_hierarchicalresourcequotas.yaml b/config/crd/bases/hnc.x-k8s.io_hierarchicalresourcequotas.yaml index 903977884..915178801 100644 --- a/config/crd/bases/hnc.x-k8s.io_hierarchicalresourcequotas.yaml +++ b/config/crd/bases/hnc.x-k8s.io_hierarchicalresourcequotas.yaml @@ -16,7 +16,14 @@ spec: singular: hierarchicalresourcequota scope: Namespaced versions: - - name: v1alpha2 + - additionalPrinterColumns: + - jsonPath: .status.requestsSummary + name: Request + type: string + - jsonPath: .status.limitsSummary + name: Limit + type: string + name: v1alpha2 schema: openAPIV3Schema: description: HierarchicalResourceQuota sets aggregate quota restrictions enforced @@ -62,6 +69,14 @@ spec: description: Hard is the set of enforced hard limits for each named resource type: object + limitsSummary: + description: LimitsSummary is used by kubectl get hrq, and summarizes + the relevant information from .status.hard.limits and .status.used.limits. + type: string + requestsSummary: + description: RequestsSummary is used by kubectl get hrq, and summarizes + the relevant information from .status.hard.requests and .status.used.requests. + type: string used: additionalProperties: anyOf: diff --git a/internal/hrq/hrqreconciler.go b/internal/hrq/hrqreconciler.go index 3929523bb..a340bdeff 100644 --- a/internal/hrq/hrqreconciler.go +++ b/internal/hrq/hrqreconciler.go @@ -1,8 +1,11 @@ package hrq import ( + "bytes" "context" "fmt" + "sort" + "strings" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/errors" @@ -13,6 +16,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/source" + v1 "k8s.io/api/core/v1" api "sigs.k8s.io/hierarchical-namespaces/api/v1alpha2" "sigs.k8s.io/hierarchical-namespaces/internal/forest" "sigs.k8s.io/hierarchical-namespaces/internal/hrq/utils" @@ -144,6 +148,33 @@ func (r *HierarchicalResourceQuotaReconciler) syncUsages(inst *api.HierarchicalR // Filter the usages to only include the resource types being limited by this HRQ and write those // usages back to the HRQ status. inst.Status.Used = utils.FilterUnlimited(ns.GetSubtreeUsages(), inst.Spec.Hard) + + // Update status.request and status.limit to show HRQ status by using kubectl get + resources := make([]v1.ResourceName, 0, len(inst.Status.Hard)) + for resource := range inst.Status.Hard { + resources = append(resources, resource) + } + sort.Sort(sortableResourceNames(resources)) + + requestColumn := bytes.NewBuffer([]byte{}) + limitColumn := bytes.NewBuffer([]byte{}) + for i := range resources { + w := requestColumn + resource := resources[i] + usedQuantity := inst.Status.Used[resource] + hardQuantity := inst.Status.Hard[resource] + + // use limitColumn writer if a resource name prefixed with "limits" is found + if pieces := strings.Split(resource.String(), "."); len(pieces) > 1 && pieces[0] == "limits" { + w = limitColumn + } + + fmt.Fprintf(w, "%s: %s/%s, ", resource, usedQuantity.String(), hardQuantity.String()) + } + + inst.Status.RequestsSummary = strings.TrimSuffix(requestColumn.String(), ", ") + inst.Status.LimitsSummary = strings.TrimSuffix(limitColumn.String(), ", ") + } func isDeleted(inst *api.HierarchicalResourceQuota) bool { @@ -203,3 +234,18 @@ func (r *HierarchicalResourceQuotaReconciler) SetupWithManager(mgr ctrl.Manager) Watches(&source.Channel{Source: r.trigger}, &handler.EnqueueRequestForObject{}). Complete(r) } + +// sortableResourceNames - An array of sortable resource names +type sortableResourceNames []v1.ResourceName + +func (list sortableResourceNames) Len() int { + return len(list) +} + +func (list sortableResourceNames) Swap(i, j int) { + list[i], list[j] = list[j], list[i] +} + +func (list sortableResourceNames) Less(i, j int) bool { + return list[i] < list[j] +} diff --git a/test/e2e/hrq_test.go b/test/e2e/hrq_test.go index 9b947a9aa..fe3a331a9 100644 --- a/test/e2e/hrq_test.go +++ b/test/e2e/hrq_test.go @@ -296,6 +296,32 @@ var _ = PDescribe("Hierarchical Resource Quota", func() { createPod("pod2", nsC, "memory 200Mi cpu 300m", "memory 100Mi cpu 150m") }) + It("should get HRQ status using kubectl get hrq command", func() { + // set up namespaces + CreateNamespace(nsA) + CreateSubnamespace(nsB, nsA) + + // Set up an HRQ with limits of 2 secrets in namespace a. + setHRQ("a-hrq", nsA, "secrets", "2") + // Set up an HRQ with limits of 4 pvcs in namespace a. + setHRQ("a-hrq-another", nsA, "persistentvolumeclaims", "4") + // Set up an HRQ with limits of 3 pvcs in namespace b. + setHRQ("b-hrq", nsB, "persistentvolumeclaims", "3") + + // Should allow creating a secret in namespace b. + Eventually(createSecret("b-secret", nsB)).Should(Succeed()) + // Should allow creating a pvc in namespace a. + Eventually(createPVC("a-pvc", nsA)).Should(Succeed()) + // Should allow creating a pvc in namespace b. + Eventually(createPVC("b-pvc", nsB)).Should(Succeed()) + + // Verify the HRQ status are shown. + RunShouldContainMultiple([]string{"a-hrq", "secrets: 1/2"}, propogationTimeout, "kubectl get hrq", "a-hrq", "-n", nsA) + RunShouldContainMultiple([]string{"b-hrq", "persistentvolumeclaims: 1/3"}, propogationTimeout, "kubectl get hrq", "b-hrq", "-n", nsB) + RunShouldContainMultiple([]string{"a-hrq", "a-hrq-another"}, propogationTimeout, "kubectl get hrq", "-n", nsA) + RunShouldContainMultiple([]string{"a-hrq", "a-hrq-another", "a-hrq"}, propogationTimeout, "kubectl get hrq", "--all-namespaces") + }) + It("should get HRQ status using kubectl-hns hrq command", func() { // set up namespaces CreateNamespace(nsA)