From 445baa99d260053b51dcb9df7484986f80dabc57 Mon Sep 17 00:00:00 2001 From: Brian Pursley Date: Fri, 24 Jan 2020 10:42:08 -0500 Subject: [PATCH] Added 'No resources found' message to describe and top pod commands Kubernetes-commit: e70a630dac6c0158a5f9bb571223ed5759096dc1 --- pkg/cmd/describe/describe.go | 9 +++ pkg/cmd/describe/describe_test.go | 53 ++++++++++++++++ pkg/cmd/top/top_pod.go | 7 ++ pkg/cmd/top/top_pod_test.go | 102 ++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+) diff --git a/pkg/cmd/describe/describe.go b/pkg/cmd/describe/describe.go index 5462bd6e..0ad87047 100644 --- a/pkg/cmd/describe/describe.go +++ b/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/pkg/cmd/describe/describe_test.go b/pkg/cmd/describe/describe_test.go index 7be073bc..c306e4d0 100644 --- a/pkg/cmd/describe/describe_test.go +++ b/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/pkg/cmd/top/top_pod.go b/pkg/cmd/top/top_pod.go index 3af15bca..77b7b76a 100644 --- a/pkg/cmd/top/top_pod.go +++ b/pkg/cmd/top/top_pod.go @@ -197,6 +197,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/pkg/cmd/top/top_pod_test.go b/pkg/cmd/top/top_pod_test.go index b5c67e07..ed5f8f68 100644 --- a/pkg/cmd/top/top_pod_test.go +++ b/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