Skip to content

Commit

Permalink
Merge pull request #290 from keisukesakasai/cherry-pick-of-#286-v1.1
Browse files Browse the repository at this point in the history
Cherry pick of #283, #286, #294
  • Loading branch information
k8s-ci-robot committed Jun 12, 2023
2 parents 61562c5 + 131d496 commit ffd2763
Show file tree
Hide file tree
Showing 104 changed files with 13,580 additions and 2,344 deletions.
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
2 changes: 1 addition & 1 deletion vendor/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go

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

0 comments on commit ffd2763

Please sign in to comment.