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

Cherry pick of #283, #286, #294 #290

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ require (
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/aws/aws-sdk-go v1.23.20 // indirect
github.com/aws/aws-sdk-go v1.38.49 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/census-instrumentation/opencensus-proto v0.2.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
Expand Down Expand Up @@ -63,10 +63,10 @@ require (
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
Expand Down Expand Up @@ -99,9 +99,8 @@ require (
golang.org/x/text v0.5.0
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/tools v0.4.1-0.20221208213631-3f74d914ae6d // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/api v0.44.0 // indirect
google.golang.org/api v0.46.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 // indirect
google.golang.org/grpc v1.40.0 // indirect
Expand All @@ -120,7 +119,6 @@ require (
sigs.k8s.io/yaml v1.3.0 // indirect
)

require (
github.com/spf13/afero v1.6.0 // indirect
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20230208013708-22718275bffe // indirect
)
require sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20230208013708-22718275bffe

require github.com/spf13/afero v1.6.0 // indirect
43 changes: 12 additions & 31 deletions go.sum

Large diffs are not rendered by default.

188 changes: 188 additions & 0 deletions internal/kubectl/hrq.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package kubectl

import (
"bytes"
"fmt"
"os"
"sort"
"strings"
"time"

"github.com/liggitt/tabwriter"
"github.com/spf13/cobra"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/cli-runtime/pkg/printers"
api "sigs.k8s.io/hierarchical-namespaces/api/v1alpha2"
)

const (
tabwriterMinWidth = 6
tabwriterWidth = 4
tabwriterPadding = 3
tabwriterPadChar = ' '
tabwriterFlags = tabwriter.RememberWidths
)

var namespace string

// Define HierarchicalResourceQuota Table Column
var hierarchicalResourceQuotaColumnDefinitions = []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
{Name: "Request", Type: "string", Description: "Request represents a minimum amount of cpu/memory that a container may consume."},
{Name: "Limit", Type: "string", Description: "Limits control the maximum amount of cpu/memory that a container may use independent of contention on the node."},
}

var hrqCmd = &cobra.Command{
Use: "hrq [NAME]",
Short: "Display one or more HierarchicalResourceQuota",
Run: Run,
}

func Run(cmd *cobra.Command, args []string) {
flags := cmd.Flags()
table := &metav1.Table{ColumnDefinitions: hierarchicalResourceQuotaColumnDefinitions}

showLabels := flags.Changed("show-labels")

allResourcesNamespaced := !flags.Changed("all-namespaces")
if !allResourcesNamespaced {
namespace = ""
}

// Get HierarchicalResourceQuotaList from the specified namespace
hrqList := client.getHRQ(args, namespace)

if len(hrqList.Items) < 1 {
if allResourcesNamespaced {
fmt.Printf("No resources found in %s namespace.\n", namespace)
os.Exit(1)
} else {
fmt.Println("No resources found")
os.Exit(1)
}
}

// Create []metav1.TableRow from HierarchicalResourceQuotaList
tableRaws, err := printHierarchicalResourceQuotaList(hrqList)
if err != nil {
fmt.Printf("Error reading hierarchicalresourcequotas: %s\n", err)
}
table.Rows = tableRaws

// Create writer
w := tabwriter.NewWriter(os.Stdout, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, tabwriterFlags)

// Create TablePrinter
p := printers.NewTablePrinter(printers.PrintOptions{
NoHeaders: false,
WithNamespace: !allResourcesNamespaced,
WithKind: true,
Wide: true,
ShowLabels: showLabels,
Kind: schema.GroupKind{},
ColumnLabels: nil,
SortBy: "",
AllowMissingKeys: false,
})

p.PrintObj(table, w)

w.Flush()
}

func printHierarchicalResourceQuota(hierarchicalResourceQuota *api.HierarchicalResourceQuota) ([]metav1.TableRow, error) {
row := metav1.TableRow{
Object: runtime.RawExtension{Object: hierarchicalResourceQuota},
}

resources := make([]v1.ResourceName, 0, len(hierarchicalResourceQuota.Status.Hard))
for resource := range hierarchicalResourceQuota.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 := hierarchicalResourceQuota.Status.Used[resource]
hardQuantity := hierarchicalResourceQuota.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())
}

age := translateTimestampSince(hierarchicalResourceQuota.CreationTimestamp)
row.Cells = append(row.Cells, hierarchicalResourceQuota.Name, age, strings.TrimSuffix(requestColumn.String(), ", "), strings.TrimSuffix(limitColumn.String(), ", "))
return []metav1.TableRow{row}, nil
}

func printHierarchicalResourceQuotaList(list *api.HierarchicalResourceQuotaList) ([]metav1.TableRow, error) {
rows := make([]metav1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printHierarchicalResourceQuota(&list.Items[i])
if err != nil {
return nil, err
}
rows = append(rows, r...)
}
return rows, nil
}

// translateTimestampSince returns the elapsed time since timestamp in
// human-readable approximation.
func translateTimestampSince(timestamp metav1.Time) string {
if timestamp.IsZero() {
return "<unknown>"
}

return duration.HumanDuration(time.Since(timestamp.Time))
}

// 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]
}

func newHrqCmd() *cobra.Command {
hrqCmd.Flags().StringVarP(&namespace, "namespace", "n", v1.NamespaceDefault, "If present, the namespace scope for this CLI request")

hrqCmd.Flags().BoolP("all-namespaces", "A", false, "Displays all HierarchicalResourceQuota on the cluster")
hrqCmd.Flags().BoolP("show-labels", "", false, "Displays Labels of HierarchicalResourceQuota")
return hrqCmd
}
26 changes: 26 additions & 0 deletions internal/kubectl/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type Client interface {
getAnchorStatus(nnm string) anchorStatus
getHNCConfig() *api.HNCConfiguration
updateHNCConfig(*api.HNCConfiguration)
getHRQ(names []string, nnm string) *api.HierarchicalResourceQuotaList
}

func init() {
Expand Down Expand Up @@ -103,6 +104,7 @@ func init() {
rootCmd.AddCommand(newCreateCmd())
rootCmd.AddCommand(newConfigCmd())
rootCmd.AddCommand(newVersionCmd())
rootCmd.AddCommand(newHrqCmd())
}

func Execute() {
Expand Down Expand Up @@ -213,3 +215,27 @@ func (cl *realClient) updateHNCConfig(config *api.HNCConfiguration) {
os.Exit(1)
}
}

func (cl *realClient) getHRQ(names []string, nnm string) *api.HierarchicalResourceQuotaList {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
hrqList := &api.HierarchicalResourceQuotaList{}

if len(names) > 0 && nnm != "" {
for _, name := range names {
hrq := &api.HierarchicalResourceQuota{}
if err := hncClient.Get().Resource("hierarchicalresourcequotas").Namespace(namespace).Name(name).Do(ctx).Into(hrq); err != nil {
fmt.Printf("Error reading hierarchicalresourcequota %s: %s\n", name, err)
os.Exit(1)
}

hrqList.Items = append(hrqList.Items, *hrq)
}
} else {
if err := hncClient.Get().Resource("hierarchicalresourcequotas").Namespace(namespace).Do(ctx).Into(hrqList); err != nil {
fmt.Printf("Error reading hierarchicalresourcequotas: %s\n", err)
os.Exit(1)
}
}
return hrqList
}
42 changes: 34 additions & 8 deletions test/e2e/hrq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import (

const (
prefix = "hrq-test-"
nsA = prefix+"a"
nsB = prefix+"b"
nsC = prefix+"c"
nsD = prefix+"d"
nsA = prefix + "a"
nsB = prefix + "b"
nsC = prefix + "c"
nsD = prefix + "d"

propagationTime = 5
propagationTime = 5
resourceQuotaSingleton = "hrq.hnc.x-k8s.io"
)

Expand Down Expand Up @@ -295,6 +295,32 @@ var _ = PDescribe("Hierarchical Resource Quota", func() {
// Should allow creating another pod under limit.
createPod("pod2", nsC, "memory 200Mi cpu 300m", "memory 100Mi cpu 150m")
})

It("should get HRQ status using kubectl-hns 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 hns hrq", "a-hrq", "-n", nsA)
RunShouldContainMultiple([]string{"b-hrq", "persistentvolumeclaims: 1/3"}, propogationTimeout, "kubectl hns hrq", "b-hrq", "-n", nsB)
RunShouldContainMultiple([]string{"a-hrq", "a-hrq-another"}, propogationTimeout, "kubectl hns hrq", "-n", nsA)
RunShouldContainMultiple([]string{"a-hrq", "a-hrq-another", "a-hrq"}, propogationTimeout, "kubectl hns hrq", "--all-namespaces")
})
})

func generateHRQManifest(nm, nsnm string, args ...string) string {
Expand Down Expand Up @@ -340,8 +366,8 @@ func createPVC(nm, nsnm string) func() error {
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: `+nm+`
namespace: `+nsnm+`
name: ` + nm + `
namespace: ` + nsnm + `
spec:
storageClassName: manual
accessModes:
Expand All @@ -350,7 +376,7 @@ spec:
requests:
storage: 1Gi`
fn := writeTempFile(pvc)
GinkgoT().Log("Wrote "+fn+":\n"+pvc)
GinkgoT().Log("Wrote " + fn + ":\n" + pvc)
defer removeFile(fn)
return TryRun("kubectl apply -f", fn)
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions vendor/github.com/aws/aws-sdk-go/aws/client/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading