Skip to content
This repository was archived by the owner on Apr 17, 2025. It is now read-only.

Commit 97817c0

Browse files
authored
Merge pull request #283 from mochizuki875/feature_cmd_hrq
Show HierarchicalResourceQuota status by kubectl-hns
2 parents fa104e8 + d387a2d commit 97817c0

File tree

109 files changed

+14246
-2349
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+14246
-2349
lines changed

go.mod

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
k8s.io/apimachinery v0.23.2
2121
k8s.io/cli-runtime v0.23.2
2222
k8s.io/client-go v0.23.2
23+
k8s.io/kubernetes v1.23.2
2324
sigs.k8s.io/controller-runtime v0.11.0
2425
sigs.k8s.io/controller-tools v0.8.0
2526
)
@@ -35,7 +36,7 @@ require (
3536
github.com/BurntSushi/toml v1.2.1 // indirect
3637
github.com/PuerkitoBio/purell v1.1.1 // indirect
3738
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
38-
github.com/aws/aws-sdk-go v1.23.20 // indirect
39+
github.com/aws/aws-sdk-go v1.38.49 // indirect
3940
github.com/beorn7/perks v1.0.1 // indirect
4041
github.com/census-instrumentation/opencensus-proto v0.2.1 // indirect
4142
github.com/cespare/xxhash/v2 v2.1.1 // indirect
@@ -63,10 +64,10 @@ require (
6364
github.com/googleapis/gnostic v0.5.5 // indirect
6465
github.com/imdario/mergo v0.3.12 // indirect
6566
github.com/inconshreveable/mousetrap v1.0.0 // indirect
66-
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
67+
github.com/jmespath/go-jmespath v0.4.0 // indirect
6768
github.com/josharian/intern v1.0.0 // indirect
6869
github.com/json-iterator/go v1.1.12 // indirect
69-
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
70+
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
7071
github.com/mailru/easyjson v0.7.6 // indirect
7172
github.com/mattn/go-colorable v0.1.8 // indirect
7273
github.com/mattn/go-isatty v0.0.12 // indirect
@@ -99,9 +100,8 @@ require (
99100
golang.org/x/text v0.5.0
100101
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
101102
golang.org/x/tools v0.4.1-0.20221208213631-3f74d914ae6d // indirect
102-
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
103103
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
104-
google.golang.org/api v0.44.0 // indirect
104+
google.golang.org/api v0.46.0 // indirect
105105
google.golang.org/appengine v1.6.7 // indirect
106106
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 // indirect
107107
google.golang.org/grpc v1.40.0 // indirect
@@ -120,7 +120,34 @@ require (
120120
sigs.k8s.io/yaml v1.3.0 // indirect
121121
)
122122

123-
require (
124-
github.com/spf13/afero v1.6.0 // indirect
125-
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20230208013708-22718275bffe // indirect
123+
require sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20230208013708-22718275bffe
124+
125+
require github.com/spf13/afero v1.6.0 // indirect
126+
127+
replace (
128+
k8s.io/api => k8s.io/api v0.23.2
129+
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.2
130+
k8s.io/apimachinery => k8s.io/apimachinery v0.23.2
131+
k8s.io/apiserver => k8s.io/apiserver v0.23.2
132+
k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.2
133+
k8s.io/client-go => k8s.io/client-go v0.23.2
134+
k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.2
135+
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.2
136+
k8s.io/code-generator => k8s.io/code-generator v0.23.2
137+
k8s.io/component-base => k8s.io/component-base v0.23.2
138+
k8s.io/component-helpers => k8s.io/component-helpers v0.23.2
139+
k8s.io/controller-manager => k8s.io/controller-manager v0.23.2
140+
k8s.io/cri-api => k8s.io/cri-api v0.23.2
141+
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.2
142+
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.2
143+
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.2
144+
k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.2
145+
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.2
146+
k8s.io/kubectl => k8s.io/kubectl v0.23.2
147+
k8s.io/kubelet => k8s.io/kubelet v0.23.2
148+
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.2
149+
k8s.io/metrics => k8s.io/metrics v0.23.2
150+
k8s.io/mount-utils => k8s.io/mount-utils v0.23.2
151+
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.23.2
152+
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.2
126153
)

go.sum

Lines changed: 187 additions & 31 deletions
Large diffs are not rendered by default.

internal/kubectl/hrq.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
*/
15+
16+
package kubectl
17+
18+
import (
19+
"bytes"
20+
"fmt"
21+
"os"
22+
"sort"
23+
"strings"
24+
"time"
25+
26+
"github.com/liggitt/tabwriter"
27+
"github.com/spf13/cobra"
28+
v1 "k8s.io/api/core/v1"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/runtime"
31+
"k8s.io/apimachinery/pkg/runtime/schema"
32+
"k8s.io/apimachinery/pkg/util/duration"
33+
"k8s.io/cli-runtime/pkg/printers"
34+
k8sprinters "k8s.io/kubernetes/pkg/printers"
35+
api "sigs.k8s.io/hierarchical-namespaces/api/v1alpha2"
36+
)
37+
38+
const (
39+
tabwriterMinWidth = 6
40+
tabwriterWidth = 4
41+
tabwriterPadding = 3
42+
tabwriterPadChar = ' '
43+
tabwriterFlags = tabwriter.RememberWidths
44+
)
45+
46+
var namespace string
47+
48+
// Define HierarchicalResourceQuota Table Column
49+
var hierarchicalResourceQuotaColumnDefinitions = []metav1.TableColumnDefinition{
50+
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
51+
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
52+
{Name: "Request", Type: "string", Description: "Request represents a minimum amount of cpu/memory that a container may consume."},
53+
{Name: "Limit", Type: "string", Description: "Limits control the maximum amount of cpu/memory that a container may use independent of contention on the node."},
54+
}
55+
56+
var hrqCmd = &cobra.Command{
57+
Use: "hrq [NAME]",
58+
Short: "Display one or more HierarchicalResourceQuota",
59+
Run: Run,
60+
}
61+
62+
func Run(cmd *cobra.Command, args []string) {
63+
flags := cmd.Flags()
64+
table := &metav1.Table{ColumnDefinitions: hierarchicalResourceQuotaColumnDefinitions}
65+
66+
option := k8sprinters.GenerateOptions{
67+
NoHeaders: true,
68+
Wide: true,
69+
}
70+
71+
showLabels := flags.Changed("show-labels")
72+
73+
allResourcesNamespaced := !flags.Changed("all-namespaces")
74+
if !allResourcesNamespaced {
75+
namespace = ""
76+
}
77+
78+
// Get HierarchicalResourceQuotaList from the specified namespace
79+
hrqList := client.getHRQ(args, namespace)
80+
81+
if len(hrqList.Items) < 1 {
82+
if allResourcesNamespaced {
83+
fmt.Printf("No resources found in %s namespace.\n", namespace)
84+
os.Exit(1)
85+
} else {
86+
fmt.Println("No resources found")
87+
os.Exit(1)
88+
}
89+
}
90+
91+
// Create []metav1.TableRow from HierarchicalResourceQuotaList
92+
tableRaws, err := printHierarchicalResourceQuotaList(hrqList, option)
93+
if err != nil {
94+
fmt.Printf("Error reading hierarchicalresourcequotas: %s\n", err)
95+
}
96+
table.Rows = tableRaws
97+
98+
// Create writer
99+
w := tabwriter.NewWriter(os.Stdout, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, tabwriterFlags)
100+
101+
// Create TablePrinter
102+
p := printers.NewTablePrinter(printers.PrintOptions{
103+
NoHeaders: false,
104+
WithNamespace: !allResourcesNamespaced,
105+
WithKind: true,
106+
Wide: true,
107+
ShowLabels: showLabels,
108+
Kind: schema.GroupKind{},
109+
ColumnLabels: nil,
110+
SortBy: "",
111+
AllowMissingKeys: false,
112+
})
113+
114+
p.PrintObj(table, w)
115+
116+
w.Flush()
117+
}
118+
119+
func printHierarchicalResourceQuota(hierarchicalResourceQuota *api.HierarchicalResourceQuota, options k8sprinters.GenerateOptions) ([]metav1.TableRow, error) {
120+
row := metav1.TableRow{
121+
Object: runtime.RawExtension{Object: hierarchicalResourceQuota},
122+
}
123+
124+
resources := make([]v1.ResourceName, 0, len(hierarchicalResourceQuota.Status.Hard))
125+
for resource := range hierarchicalResourceQuota.Status.Hard {
126+
resources = append(resources, resource)
127+
}
128+
sort.Sort(SortableResourceNames(resources))
129+
130+
requestColumn := bytes.NewBuffer([]byte{})
131+
limitColumn := bytes.NewBuffer([]byte{})
132+
for i := range resources {
133+
w := requestColumn
134+
resource := resources[i]
135+
usedQuantity := hierarchicalResourceQuota.Status.Used[resource]
136+
hardQuantity := hierarchicalResourceQuota.Status.Hard[resource]
137+
138+
// use limitColumn writer if a resource name prefixed with "limits" is found
139+
if pieces := strings.Split(resource.String(), "."); len(pieces) > 1 && pieces[0] == "limits" {
140+
w = limitColumn
141+
}
142+
143+
fmt.Fprintf(w, "%s: %s/%s, ", resource, usedQuantity.String(), hardQuantity.String())
144+
}
145+
146+
age := translateTimestampSince(hierarchicalResourceQuota.CreationTimestamp)
147+
row.Cells = append(row.Cells, hierarchicalResourceQuota.Name, age, strings.TrimSuffix(requestColumn.String(), ", "), strings.TrimSuffix(limitColumn.String(), ", "))
148+
return []metav1.TableRow{row}, nil
149+
}
150+
151+
func printHierarchicalResourceQuotaList(list *api.HierarchicalResourceQuotaList, options k8sprinters.GenerateOptions) ([]metav1.TableRow, error) {
152+
rows := make([]metav1.TableRow, 0, len(list.Items))
153+
for i := range list.Items {
154+
r, err := printHierarchicalResourceQuota(&list.Items[i], options)
155+
if err != nil {
156+
return nil, err
157+
}
158+
rows = append(rows, r...)
159+
}
160+
return rows, nil
161+
}
162+
163+
// translateTimestampSince returns the elapsed time since timestamp in
164+
// human-readable approximation.
165+
func translateTimestampSince(timestamp metav1.Time) string {
166+
if timestamp.IsZero() {
167+
return "<unknown>"
168+
}
169+
170+
return duration.HumanDuration(time.Since(timestamp.Time))
171+
}
172+
173+
// SortableResourceNames - An array of sortable resource names
174+
type SortableResourceNames []v1.ResourceName
175+
176+
func (list SortableResourceNames) Len() int {
177+
return len(list)
178+
}
179+
180+
func (list SortableResourceNames) Swap(i, j int) {
181+
list[i], list[j] = list[j], list[i]
182+
}
183+
184+
func (list SortableResourceNames) Less(i, j int) bool {
185+
return list[i] < list[j]
186+
}
187+
188+
func newHrqCmd() *cobra.Command {
189+
hrqCmd.Flags().StringVarP(&namespace, "namespace", "n", v1.NamespaceDefault, "If present, the namespace scope for this CLI request")
190+
191+
hrqCmd.Flags().BoolP("all-namespaces", "A", false, "Displays all HierarchicalResourceQuota on the cluster")
192+
hrqCmd.Flags().BoolP("show-labels", "", false, "Displays Labels of HierarchicalResourceQuota")
193+
return hrqCmd
194+
}

internal/kubectl/root.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type Client interface {
5252
getAnchorStatus(nnm string) anchorStatus
5353
getHNCConfig() *api.HNCConfiguration
5454
updateHNCConfig(*api.HNCConfiguration)
55+
getHRQ(names []string, nnm string) *api.HierarchicalResourceQuotaList
5556
}
5657

5758
func init() {
@@ -103,6 +104,7 @@ func init() {
103104
rootCmd.AddCommand(newCreateCmd())
104105
rootCmd.AddCommand(newConfigCmd())
105106
rootCmd.AddCommand(newVersionCmd())
107+
rootCmd.AddCommand(newHrqCmd())
106108
}
107109

108110
func Execute() {
@@ -213,3 +215,27 @@ func (cl *realClient) updateHNCConfig(config *api.HNCConfiguration) {
213215
os.Exit(1)
214216
}
215217
}
218+
219+
func (cl *realClient) getHRQ(names []string, nnm string) *api.HierarchicalResourceQuotaList {
220+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
221+
defer cancel()
222+
hrqList := &api.HierarchicalResourceQuotaList{}
223+
224+
if len(names) > 0 && nnm != "" {
225+
for _, name := range names {
226+
hrq := &api.HierarchicalResourceQuota{}
227+
if err := hncClient.Get().Resource("hierarchicalresourcequotas").Namespace(namespace).Name(name).Do(ctx).Into(hrq); err != nil {
228+
fmt.Printf("Error reading hierarchicalresourcequota %s: %s\n", name, err)
229+
os.Exit(1)
230+
}
231+
232+
hrqList.Items = append(hrqList.Items, *hrq)
233+
}
234+
} else {
235+
if err := hncClient.Get().Resource("hierarchicalresourcequotas").Namespace(namespace).Do(ctx).Into(hrqList); err != nil {
236+
fmt.Printf("Error reading hierarchicalresourcequotas: %s\n", err)
237+
os.Exit(1)
238+
}
239+
}
240+
return hrqList
241+
}

test/e2e/hrq_test.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ import (
1919

2020
const (
2121
prefix = "hrq-test-"
22-
nsA = prefix+"a"
23-
nsB = prefix+"b"
24-
nsC = prefix+"c"
25-
nsD = prefix+"d"
22+
nsA = prefix + "a"
23+
nsB = prefix + "b"
24+
nsC = prefix + "c"
25+
nsD = prefix + "d"
2626

27-
propagationTime = 5
27+
propagationTime = 5
2828
resourceQuotaSingleton = "hrq.hnc.x-k8s.io"
2929
)
3030

@@ -295,6 +295,32 @@ var _ = PDescribe("Hierarchical Resource Quota", func() {
295295
// Should allow creating another pod under limit.
296296
createPod("pod2", nsC, "memory 200Mi cpu 300m", "memory 100Mi cpu 150m")
297297
})
298+
299+
It("should get HRQ status using kubectl-hns hrq command", func() {
300+
// set up namespaces
301+
CreateNamespace(nsA)
302+
CreateSubnamespace(nsB, nsA)
303+
304+
// Set up an HRQ with limits of 2 secrets in namespace a.
305+
setHRQ("a-hrq", nsA, "secrets", "2")
306+
// Set up an HRQ with limits of 4 pvcs in namespace a.
307+
setHRQ("a-hrq-another", nsA, "persistentvolumeclaims", "4")
308+
// Set up an HRQ with limits of 3 pvcs in namespace b.
309+
setHRQ("b-hrq", nsB, "persistentvolumeclaims", "3")
310+
311+
// Should allow creating a secret in namespace b.
312+
Eventually(createSecret("b-secret", nsB)).Should(Succeed())
313+
// Should allow creating a pvc in namespace a.
314+
Eventually(createPVC("a-pvc", nsA)).Should(Succeed())
315+
// Should allow creating a pvc in namespace b.
316+
Eventually(createPVC("b-pvc", nsB)).Should(Succeed())
317+
318+
// Verify the HRQ status are shown.
319+
RunShouldContainMultiple([]string{"a-hrq", "secrets: 1/2"}, propogationTimeout, "kubectl hns hrq", "a-hrq", "-n", nsA)
320+
RunShouldContainMultiple([]string{"b-hrq", "persistentvolumeclaims: 1/3"}, propogationTimeout, "kubectl hns hrq", "b-hrq", "-n", nsB)
321+
RunShouldContainMultiple([]string{"a-hrq", "a-hrq-another"}, propogationTimeout, "kubectl hns hrq", "-n", nsA)
322+
RunShouldContainMultiple([]string{"a-hrq", "a-hrq-another", "a-hrq"}, propogationTimeout, "kubectl hns hrq", "--all-namespaces")
323+
})
298324
})
299325

300326
func generateHRQManifest(nm, nsnm string, args ...string) string {
@@ -340,8 +366,8 @@ func createPVC(nm, nsnm string) func() error {
340366
apiVersion: v1
341367
kind: PersistentVolumeClaim
342368
metadata:
343-
name: `+nm+`
344-
namespace: `+nsnm+`
369+
name: ` + nm + `
370+
namespace: ` + nsnm + `
345371
spec:
346372
storageClassName: manual
347373
accessModes:
@@ -350,7 +376,7 @@ spec:
350376
requests:
351377
storage: 1Gi`
352378
fn := writeTempFile(pvc)
353-
GinkgoT().Log("Wrote "+fn+":\n"+pvc)
379+
GinkgoT().Log("Wrote " + fn + ":\n" + pvc)
354380
defer removeFile(fn)
355381
return TryRun("kubectl apply -f", fn)
356382
}

vendor/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)