Skip to content

Commit fa02475

Browse files
authored
Show HierarchicalResourceQuota status by kubectl get (#17)
Co-authored-by: @mochizuki875
1 parent 731e587 commit fa02475

File tree

4 files changed

+120
-1
lines changed

4 files changed

+120
-1
lines changed

api/v1alpha2/hierarchicalresourcequota_types.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,21 @@ type HierarchicalResourceQuotaStatus struct {
4848
// and its descendant namespaces.
4949
// +optional
5050
Used corev1.ResourceList `json:"used,omitempty"`
51+
// RequestsSummary is used by kubectl get hrq, and summarizes the relevant information
52+
// from .status.hard.requests and .status.used.requests.
53+
// +optional
54+
RequestsSummary string `json:"requestsSummary,omitempty"`
55+
// LimitsSummary is used by kubectl get hrq, and summarizes the relevant information
56+
// from .status.hard.limits and .status.used.limits.
57+
// +optional
58+
LimitsSummary string `json:"limitsSummary,omitempty"`
5159
}
5260

5361
// +kubebuilder:object:root=true
5462
// +kubebuilder:resource:path=hierarchicalresourcequotas,shortName=hrq,scope=Namespaced
63+
// +kubebuilder:printcolumn:name="REQUEST",type="string",JSONPath=".status.requestsSummary",description="Current usage of resources"
64+
// +kubebuilder:printcolumn:name="LIMIT",type="string",JSONPath=".status.limitsSummary",description="Resource limits"
65+
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
5566

5667
// HierarchicalResourceQuota sets aggregate quota restrictions enforced for a
5768
// namespace and descendant namespaces

config/crd/bases/hnc.x-k8s.io_hierarchicalresourcequotas.yaml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,19 @@ spec:
1616
singular: hierarchicalresourcequota
1717
scope: Namespaced
1818
versions:
19-
- name: v1alpha2
19+
- additionalPrinterColumns:
20+
- description: Current usage of resources
21+
jsonPath: .status.requestsSummary
22+
name: REQUEST
23+
type: string
24+
- description: Resource limits
25+
jsonPath: .status.limitsSummary
26+
name: LIMIT
27+
type: string
28+
- jsonPath: .metadata.creationTimestamp
29+
name: AGE
30+
type: date
31+
name: v1alpha2
2032
schema:
2133
openAPIV3Schema:
2234
description: HierarchicalResourceQuota sets aggregate quota restrictions enforced
@@ -99,6 +111,14 @@ spec:
99111
description: Hard is the set of enforced hard limits for each named
100112
resource
101113
type: object
114+
limitsSummary:
115+
description: LimitsSummary is used by kubectl get hrq, and summarizes
116+
the relevant information from .status.hard.limits and .status.used.limits.
117+
type: string
118+
requestsSummary:
119+
description: RequestsSummary is used by kubectl get hrq, and summarizes
120+
the relevant information from .status.hard.requests and .status.used.requests.
121+
type: string
102122
used:
103123
additionalProperties:
104124
anyOf:
@@ -113,3 +133,4 @@ spec:
113133
type: object
114134
served: true
115135
storage: true
136+
subresources: {}

internal/hrq/hrqreconciler.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package hrq
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
7+
"sort"
8+
"strings"
69

710
"github.com/go-logr/logr"
811
"k8s.io/apimachinery/pkg/api/errors"
@@ -14,6 +17,7 @@ import (
1417
"sigs.k8s.io/controller-runtime/pkg/handler"
1518
"sigs.k8s.io/controller-runtime/pkg/source"
1619

20+
v1 "k8s.io/api/core/v1"
1721
api "sigs.k8s.io/hierarchical-namespaces/api/v1alpha2"
1822
"sigs.k8s.io/hierarchical-namespaces/internal/forest"
1923
"sigs.k8s.io/hierarchical-namespaces/internal/hrq/utils"
@@ -186,6 +190,33 @@ func (r *HierarchicalResourceQuotaReconciler) syncUsages(inst *api.HierarchicalR
186190
}
187191
inst.Status.Used = utils.FilterUnlimited(usage, inst.Spec.Hard)
188192

193+
// Update status.request and status.limit to show HRQ status by using kubectl get
194+
resources := make([]v1.ResourceName, 0, len(inst.Status.Hard))
195+
for resource := range inst.Status.Hard {
196+
resources = append(resources, resource)
197+
}
198+
sort.Sort(sortableResourceNames(resources))
199+
200+
requestColumn := bytes.NewBuffer([]byte{})
201+
limitColumn := bytes.NewBuffer([]byte{})
202+
for i := range resources {
203+
resource := resources[i]
204+
205+
var w *bytes.Buffer
206+
if pieces := strings.Split(resource.String(), "."); len(pieces) > 1 && pieces[0] == "limits" {
207+
w = limitColumn
208+
} else {
209+
w = requestColumn
210+
}
211+
212+
usedQuantity := inst.Status.Used[resource]
213+
hardQuantity := inst.Status.Hard[resource]
214+
fmt.Fprintf(w, "%s: %s/%s, ", resource, usedQuantity.String(), hardQuantity.String())
215+
}
216+
217+
inst.Status.RequestsSummary = strings.TrimSuffix(requestColumn.String(), ", ")
218+
inst.Status.LimitsSummary = strings.TrimSuffix(limitColumn.String(), ", ")
219+
189220
return nil
190221
}
191222

@@ -247,3 +278,18 @@ func (r *HierarchicalResourceQuotaReconciler) SetupWithManager(mgr ctrl.Manager)
247278
Watches(&source.Channel{Source: r.trigger}, &handler.EnqueueRequestForObject{}).
248279
Complete(r)
249280
}
281+
282+
// sortableResourceNames - An array of sortable resource names
283+
type sortableResourceNames []v1.ResourceName
284+
285+
func (list sortableResourceNames) Len() int {
286+
return len(list)
287+
}
288+
289+
func (list sortableResourceNames) Swap(i, j int) {
290+
list[i], list[j] = list[j], list[i]
291+
}
292+
293+
func (list sortableResourceNames) Less(i, j int) bool {
294+
return list[i] < list[j]
295+
}

test/e2e/hrq_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,47 @@ var _ = PDescribe("Hierarchical Resource Quota", Serial, func() {
323323
})
324324
})
325325

326+
var _ = Describe("HRQ Printer Columns Test", Serial, func() {
327+
BeforeEach(func() {
328+
rand.Seed(time.Now().UnixNano())
329+
CleanupTestNamespaces()
330+
})
331+
332+
AfterEach(func() {
333+
CleanupTestNamespaces()
334+
})
335+
336+
It("should show HRQ resource usage and limits in kubectl get hrq columns", func() {
337+
// set up namespaces
338+
CreateNamespace(nsA)
339+
CreateSubnamespace(nsB, nsA)
340+
341+
// Set up HRQs with specific limits
342+
setHRQ("printer-test-hrq", nsA, "memory", "200Mi", "cpu", "300m")
343+
344+
// Create resources that will show up in usage
345+
createPod("printer-test-pod", nsB, "memory 100Mi cpu 150m", "memory 50Mi cpu 75m")
346+
347+
// Wait for resource usage to be recorded
348+
FieldShouldContain("hrq", nsA, "printer-test-hrq", ".status.used", "memory:50Mi")
349+
FieldShouldContain("hrq", nsA, "printer-test-hrq", ".status.used", "cpu:75m")
350+
351+
// Now verify that kubectl get hrq shows the columns with correct values
352+
RunShouldContain("printer-test-hrq", propagationTime, "kubectl get hrq -n", nsA)
353+
RunShouldContain("memory", propagationTime, "kubectl get hrq -n", nsA)
354+
RunShouldContain("cpu", propagationTime, "kubectl get hrq -n", nsA)
355+
356+
// Verify the column headers are present
357+
rawOutput, err := RunCommand("kubectl get hrq -n", nsA)
358+
Expect(err).Should(BeNil())
359+
// The default output should include our printer columns
360+
Expect(rawOutput).Should(ContainSubstring("NAME"))
361+
Expect(rawOutput).Should(ContainSubstring("REQUEST"))
362+
Expect(rawOutput).Should(ContainSubstring("LIMIT"))
363+
Expect(rawOutput).Should(ContainSubstring("AGE"))
364+
})
365+
})
366+
326367
func generateHRQManifest(nm, nsnm string, args ...string) string {
327368
return `# temp file created by hrq_test.go
328369
apiVersion: hnc.x-k8s.io/v1alpha2

0 commit comments

Comments
 (0)