From e70a630dac6c0158a5f9bb571223ed5759096dc1 Mon Sep 17 00:00:00 2001 From: Brian Pursley Date: Fri, 24 Jan 2020 10:42:08 -0500 Subject: [PATCH 1/2] Added 'No resources found' message to describe and top pod commands --- .../kubectl/pkg/cmd/describe/describe.go | 9 ++ .../kubectl/pkg/cmd/describe/describe_test.go | 53 +++++++++ .../src/k8s.io/kubectl/pkg/cmd/top/top_pod.go | 7 ++ .../kubectl/pkg/cmd/top/top_pod_test.go | 102 ++++++++++++++++++ 4 files changed, 171 insertions(+) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe.go b/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe.go index 5462bd6e1ac..0ad87047447 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe.go @@ -203,6 +203,15 @@ func (o *DescribeOptions) Run() error { } } + if len(infos) == 0 && len(allErrs) == 0 { + // if we wrote no output, and had no errors, be sure we output something. + if o.AllNamespaces { + fmt.Fprintln(o.ErrOut, "No resources found") + } else { + fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace) + } + } + return utilerrors.NewAggregate(allErrs) } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe_test.go index 7be073bce11..c306e4d0779 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe_test.go @@ -245,6 +245,59 @@ func TestDescribeHelpMessage(t *testing.T) { } } +func TestDescribeNoResourcesFound(t *testing.T) { + testNS := "testns" + testCases := []struct { + name string + flags map[string]string + namespace string + expectedOutput string + expectedErr string + }{ + { + name: "all namespaces", + flags: map[string]string{"all-namespaces": "true"}, + expectedOutput: "", + expectedErr: "No resources found\n", + }, + { + name: "all in namespace", + namespace: testNS, + expectedOutput: "", + expectedErr: "No resources found in " + testNS + " namespace.\n", + }, + } + cmdtesting.InitTestErrorHandler(t) + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + pods, _, _ := cmdtesting.EmptyTestData() + tf := cmdtesting.NewTestFactory().WithNamespace(testNS) + defer tf.Cleanup() + codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) + + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, + Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, + } + + streams, _, buf, errbuf := genericclioptions.NewTestIOStreams() + + cmd := NewCmdDescribe("kubectl", tf, streams) + for name, value := range testCase.flags { + _ = cmd.Flags().Set(name, value) + } + cmd.Run(cmd, []string{"pods"}) + + if e, a := testCase.expectedOutput, buf.String(); e != a { + t.Errorf("Unexpected output:\nExpected:\n%v\nActual:\n%v", e, a) + } + if e, a := testCase.expectedErr, errbuf.String(); e != a { + t.Errorf("Unexpected error:\nExpected:\n%v\nActual:\n%v", e, a) + } + }) + } +} + type testDescriber struct { Name, Namespace string Settings describe.DescriberSettings diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod.go b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod.go index 04ab569fb78..b5299f1a124 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod.go @@ -196,6 +196,13 @@ func (o TopPodOptions) RunTopPod() error { if e != nil { return e } + + // if we had no errors, be sure we output something. + if o.AllNamespaces { + fmt.Fprintln(o.ErrOut, "No resources found") + } else { + fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace) + } } if err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go index b5c67e07e58..ed5f8f683fd 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go @@ -449,6 +449,108 @@ func TestTopPodWithMetricsServer(t *testing.T) { } } +func TestTopPodNoResourcesFound(t *testing.T) { + testNS := "testns" + testCases := []struct { + name string + options *TopPodOptions + namespace string + expectedOutput string + expectedErr string + expectedPath string + }{ + { + name: "all namespaces", + options: &TopPodOptions{AllNamespaces: true}, + expectedOutput: "", + expectedErr: "No resources found\n", + expectedPath: topMetricsAPIPathPrefix + "/pods", + }, + { + name: "all in namespace", + namespace: testNS, + expectedOutput: "", + expectedErr: "No resources found in " + testNS + " namespace.\n", + expectedPath: topMetricsAPIPathPrefix + "/namespaces/" + testNS + "/pods", + }, + } + cmdtesting.InitTestErrorHandler(t) + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + fakemetricsClientset := &metricsfake.Clientset{} + fakemetricsClientset.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { + res := &metricsv1beta1api.PodMetricsList{ + ListMeta: metav1.ListMeta{ + ResourceVersion: "2", + }, + Items: nil, // No metrics found + } + return true, res, nil + }) + + tf := cmdtesting.NewTestFactory().WithNamespace(testNS) + defer tf.Cleanup() + + ns := scheme.Codecs.WithoutConversion() + + tf.Client = &fake.RESTClient{ + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p := req.URL.Path; { + case p == "/api": + return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil + case p == "/apis": + return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbodyWithMetrics)))}, nil + case p == "/api/v1/namespaces/" + testNS + "/pods": + // Top Pod calls this endpoint to check if there are pods whenever it gets no metrics, + // so we need to return no pods for this test scenario + body, _ := marshallBody(metricsv1alpha1api.PodMetricsList{ + ListMeta: metav1.ListMeta{ + ResourceVersion: "2", + }, + Items: nil, // No pods found + }) + return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil + default: + t.Fatalf("%s: unexpected request: %#v\nGot URL: %#v", + testCase.name, req, req.URL) + return nil, nil + } + }), + } + tf.ClientConfigVal = cmdtesting.DefaultClientConfig() + streams, _, buf, errbuf := genericclioptions.NewTestIOStreams() + + cmd := NewCmdTopPod(tf, nil, streams) + var cmdOptions *TopPodOptions + if testCase.options != nil { + cmdOptions = testCase.options + } else { + cmdOptions = &TopPodOptions{} + } + cmdOptions.IOStreams = streams + + if err := cmdOptions.Complete(tf, cmd, nil); err != nil { + t.Fatal(err) + } + cmdOptions.MetricsClient = fakemetricsClientset + if err := cmdOptions.Validate(); err != nil { + t.Fatal(err) + } + if err := cmdOptions.RunTopPod(); err != nil { + t.Fatal(err) + } + + if e, a := testCase.expectedOutput, buf.String(); e != a { + t.Errorf("Unexpected output:\nExpected:\n%v\nActual:\n%v", e, a) + } + if e, a := testCase.expectedErr, errbuf.String(); e != a { + t.Errorf("Unexpected error:\nExpected:\n%v\nActual:\n%v", e, a) + } + }) + } +} + type fakeDiscovery struct{} // ServerGroups returns the supported groups, with information like supported versions and the From 78248d0c2aaca258b4210446dc38c096e83a1eb6 Mon Sep 17 00:00:00 2001 From: Brian Pursley Date: Tue, 4 Feb 2020 10:11:18 -0500 Subject: [PATCH 2/2] Fixed code formatting issues discovered by verify-gofmt --- .../kubectl/pkg/cmd/describe/describe_test.go | 12 ++++++------ .../src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe_test.go index c306e4d0779..34ae6bc5f4e 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/describe/describe_test.go @@ -248,15 +248,15 @@ func TestDescribeHelpMessage(t *testing.T) { func TestDescribeNoResourcesFound(t *testing.T) { testNS := "testns" testCases := []struct { - name string - flags map[string]string - namespace string - expectedOutput string - expectedErr string + name string + flags map[string]string + namespace string + expectedOutput string + expectedErr string }{ { name: "all namespaces", - flags: map[string]string{"all-namespaces": "true"}, + flags: map[string]string{"all-namespaces": "true"}, expectedOutput: "", expectedErr: "No resources found\n", }, diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go index ed5f8f683fd..a51caad6b3e 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/top/top_pod_test.go @@ -452,12 +452,12 @@ func TestTopPodWithMetricsServer(t *testing.T) { func TestTopPodNoResourcesFound(t *testing.T) { testNS := "testns" testCases := []struct { - name string - options *TopPodOptions - namespace string - expectedOutput string - expectedErr string - expectedPath string + name string + options *TopPodOptions + namespace string + expectedOutput string + expectedErr string + expectedPath string }{ { name: "all namespaces", @@ -501,7 +501,7 @@ func TestTopPodNoResourcesFound(t *testing.T) { return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil case p == "/apis": return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbodyWithMetrics)))}, nil - case p == "/api/v1/namespaces/" + testNS + "/pods": + case p == "/api/v1/namespaces/"+testNS+"/pods": // Top Pod calls this endpoint to check if there are pods whenever it gets no metrics, // so we need to return no pods for this test scenario body, _ := marshallBody(metricsv1alpha1api.PodMetricsList{