From aa96f54b9e20090111e39aa1114c694e9234212f Mon Sep 17 00:00:00 2001 From: makocchi-git Date: Mon, 1 Jul 2019 10:10:49 +0900 Subject: [PATCH 1/5] add Notice --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index acbcc79..37c8ce2 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,11 @@ kubectl free --emoji kubectl free --list --emoji ``` +## Notice + +This plugin shows just sum of requested(limited) resources, **not a real usage**. +I recommend to use `kubectl free` with `kubectl top`. + ## License This software is released under the MIT License. From 48fa884c1d3dc50d5614dd0ceaced3862b4d8a11 Mon Sep 17 00:00:00 2001 From: makocchi-git Date: Tue, 2 Jul 2019 10:50:08 +0900 Subject: [PATCH 2/5] add --all-namespaces flag --- go.mod | 5 +++++ pkg/cmd/free.go | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3fb78e7..52025e4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,12 @@ module github.com/makocchi-git/kubectl-free go 1.12 require ( + github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e // indirect + github.com/docker/docker v0.0.0-00010101000000-000000000000 // indirect github.com/gookit/color v1.1.7 + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/russross/blackfriday v0.0.0-00010101000000-000000000000 // indirect github.com/spf13/cobra v0.0.2 github.com/spf13/pflag v1.0.1 k8s.io/api v0.0.0-20190531132109-d3f5f50bdd94 diff --git a/pkg/cmd/free.go b/pkg/cmd/free.go index 8c2cb7f..252855d 100644 --- a/pkg/cmd/free.go +++ b/pkg/cmd/free.go @@ -70,6 +70,7 @@ type FreeOptions struct { header string pod bool emojiStatus bool + allNamespaces bool // unit options bytes bool @@ -120,6 +121,7 @@ func NewFreeOptions(streams genericclioptions.IOStreams) *FreeOptions { emojiStatus: false, table: table.NewOutputTable(os.Stdout), header: "default", + allNamespaces: false, } } @@ -165,6 +167,7 @@ func NewCmdFree(streams genericclioptions.IOStreams, version, commit, date strin cmd.Flags().BoolVarP(&o.listContainerImage, "list-image", "", o.listContainerImage, `Show pod list on node with container image.`) cmd.Flags().BoolVarP(&o.listAll, "list-all", "", o.listAll, `Show pods even if they have no requests/limit`) cmd.Flags().BoolVarP(&o.emojiStatus, "emoji", "", o.emojiStatus, `Let's smile!! 😃 😭`) + cmd.Flags().BoolVarP(&o.allNamespaces, "all-namespaces", "", o.allNamespaces, `If present, list pod resources(limits) across all namespaces. Namespace in current context is ignored even if specified with --namespace.`) // int64 options cmd.Flags().Int64VarP(&o.warnThreshold, "warn-threshold", "", o.warnThreshold, `Threshold of warn(yellow) color for USED column.`) @@ -199,7 +202,18 @@ func (o *FreeOptions) Prepare() error { o.nodeClient = client.CoreV1().Nodes() // pod client - o.podClient = client.CoreV1().Pods(*o.configFlags.Namespace) + if o.allNamespaces { + // --all-namespace flag + o.podClient = client.CoreV1().Pods(v1.NamespaceAll) + } else { + if *o.configFlags.Namespace == "" { + // default namespace is "default" + o.podClient = client.CoreV1().Pods(v1.NamespaceDefault) + } else { + // targeted namespace (--namespace flag) + o.podClient = client.CoreV1().Pods(*o.configFlags.Namespace) + } + } // prepare table header o.prepareFreeTableHeader() From 59ad7992fd1796efcf11ba1e13caf31f3ea5297f Mon Sep 17 00:00:00 2001 From: makocchi-git Date: Fri, 5 Jul 2019 21:13:36 +0900 Subject: [PATCH 3/5] add some test cases --- pkg/cmd/free_test.go | 112 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/free_test.go b/pkg/cmd/free_test.go index cff510e..d4d7d4e 100644 --- a/pkg/cmd/free_test.go +++ b/pkg/cmd/free_test.go @@ -79,7 +79,7 @@ var testPods = []v1.Pod{ Phase: v1.PodRunning, }, Spec: v1.PodSpec{ - NodeName: "node2", + NodeName: "node1", Containers: []v1.Container{ { Name: "container2a", @@ -102,6 +102,39 @@ var testPods = []v1.Pod{ }, }, }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod3", + Namespace: "awesome-ns", + }, + Status: v1.PodStatus{ + PodIP: "2.3.4.5", + Phase: v1.PodRunning, + }, + Spec: v1.PodSpec{ + NodeName: "node1", + Containers: []v1.Container{ + { + Name: "container3a", + Image: "centos:7", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(200, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(300, resource.DecimalSI), + }, + Requests: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(200, resource.DecimalSI), + v1.ResourceMemory: *resource.NewQuantity(300, resource.DecimalSI), + }, + }, + }, + { + Name: "container3b", + Image: "ubuntu:bionic", + }, + }, + }, + }, } func TestNewFreeOptions(t *testing.T) { @@ -260,6 +293,32 @@ func TestPrepare(t *testing.T) { return } }) + + t.Run("prepare allnamespace", func(t *testing.T) { + + o := &FreeOptions{ + configFlags: genericclioptions.NewConfigFlags(true), + allNamespaces: true, + } + + if err := o.Prepare(); err != nil { + t.Errorf("unexpected error: %v", err) + return + } + }) + + t.Run("prepare specific namespace", func(t *testing.T) { + + o := &FreeOptions{ + configFlags: genericclioptions.NewConfigFlags(true), + } + *o.configFlags.Namespace = "awesome-ns" + + if err := o.Prepare(); err != nil { + t.Errorf("unexpected error: %v", err) + return + } + }) } func TestValidate(t *testing.T) { @@ -658,12 +717,14 @@ func TestShowFree(t *testing.T) { var tests = []struct { description string pod bool + namespace string expected []string expectedErr error }{ { "default free", false, + "default", []string{ "node1 NotReady 1 4 25% 1K 4K 25%", "", @@ -673,19 +734,30 @@ func TestShowFree(t *testing.T) { { "default free --pod", true, + "default", []string{ "node1 NotReady 1 4 25% 1K 4K 25% 1 110 1", "", }, nil, }, + { + "awesome-ns free", + true, + "awesome-ns", + []string{ + "node1 NotReady 200m 4 5% 0K 4K 7% 1 110 2", + "", + }, + nil, + }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { fakeNodeClient := fake.NewSimpleClientset(&testNodes[0]) - fakePodClient := fake.NewSimpleClientset(&testPods[0]) + fakePodClient := fake.NewSimpleClientset(&testPods[0], &testPods[2]) buffer := &bytes.Buffer{} o := &FreeOptions{ @@ -694,7 +766,7 @@ func TestShowFree(t *testing.T) { list: false, pod: test.pod, nodeClient: fakeNodeClient.CoreV1().Nodes(), - podClient: fakePodClient.CoreV1().Pods(""), + podClient: fakePodClient.CoreV1().Pods(test.namespace), header: "none", } @@ -711,6 +783,40 @@ func TestShowFree(t *testing.T) { }) } + + t.Run("Allnamespace", func(t *testing.T) { + + fakeNodeClient := fake.NewSimpleClientset(&testNodes[0]) + fakePodClient := fake.NewSimpleClientset(&testPods[0], &testPods[1], &testPods[2]) + + buffer := &bytes.Buffer{} + o := &FreeOptions{ + nocolor: true, + table: table.NewOutputTable(buffer), + list: false, + pod: false, + nodeClient: fakeNodeClient.CoreV1().Nodes(), + podClient: fakePodClient.CoreV1().Pods(""), + allNamespaces: true, + header: "none", + } + + if err := o.showFree([]v1.Node{testNodes[0]}); err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + expected := []string{ + "node1 NotReady 1700m 4 42% 2K 4K 57%", + "", + } + e := strings.Join(expected, "\n") + if buffer.String() != e { + t.Errorf("expected(%s) differ (got: %s)", e, buffer.String()) + return + } + + }) } func TestListPodsOnNode(t *testing.T) { From 3670661639ae051eb046db1f6ec5d3861a40db6d Mon Sep 17 00:00:00 2001 From: makocchi-git Date: Fri, 5 Jul 2019 21:17:30 +0900 Subject: [PATCH 4/5] update usage --- README.md | 5 ++++- pkg/cmd/free.go | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 37c8ce2..512306b 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,12 @@ $ kubectl free ## Usage ```shell -# Show pod resource usage of Kubernetes nodes. +# Show pod resource usage of Kubernetes nodes (default namespace is "default"). kubectl free +# Show pod resource usage of Kubernetes nodes (all namespaces). +kubectl free --all-namespaces + # Show pod resource usage of Kubernetes nodes with number of pods and containers. kubectl free --pod diff --git a/pkg/cmd/free.go b/pkg/cmd/free.go index 252855d..a30e8e3 100644 --- a/pkg/cmd/free.go +++ b/pkg/cmd/free.go @@ -29,9 +29,12 @@ var ( // DfExample defines command examples freeExample = templates.Examples(` - # Show pod resource usage of Kubernetes nodes. + # Show pod resource usage of Kubernetes nodes (default namespace is "default"). kubectl free + # Show pod resource usage of Kubernetes nodes (all namespaces). + kubectl free --all-namespaces + # Show pod resource usage of Kubernetes nodes with number of pods and containers. kubectl free --pod From 87f44db5f24bd2f2486e7142de843eacb0d71b31 Mon Sep 17 00:00:00 2001 From: makocchi-git Date: Fri, 5 Jul 2019 21:23:38 +0900 Subject: [PATCH 5/5] fmt --- pkg/cmd/free_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/cmd/free_test.go b/pkg/cmd/free_test.go index d4d7d4e..92c8c61 100644 --- a/pkg/cmd/free_test.go +++ b/pkg/cmd/free_test.go @@ -297,7 +297,7 @@ func TestPrepare(t *testing.T) { t.Run("prepare allnamespace", func(t *testing.T) { o := &FreeOptions{ - configFlags: genericclioptions.NewConfigFlags(true), + configFlags: genericclioptions.NewConfigFlags(true), allNamespaces: true, } @@ -791,14 +791,14 @@ func TestShowFree(t *testing.T) { buffer := &bytes.Buffer{} o := &FreeOptions{ - nocolor: true, - table: table.NewOutputTable(buffer), - list: false, - pod: false, - nodeClient: fakeNodeClient.CoreV1().Nodes(), - podClient: fakePodClient.CoreV1().Pods(""), + nocolor: true, + table: table.NewOutputTable(buffer), + list: false, + pod: false, + nodeClient: fakeNodeClient.CoreV1().Nodes(), + podClient: fakePodClient.CoreV1().Pods(""), allNamespaces: true, - header: "none", + header: "none", } if err := o.showFree([]v1.Node{testNodes[0]}); err != nil {