1/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package top
18
19import (
20	"bytes"
21	"fmt"
22	"io/ioutil"
23	"net/http"
24	"reflect"
25	"strings"
26	"testing"
27
28	"net/url"
29
30	"k8s.io/api/core/v1"
31	"k8s.io/apimachinery/pkg/runtime"
32	"k8s.io/cli-runtime/pkg/genericclioptions"
33	"k8s.io/client-go/rest/fake"
34	core "k8s.io/client-go/testing"
35	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
36	"k8s.io/kubectl/pkg/scheme"
37	metricsv1alpha1api "k8s.io/metrics/pkg/apis/metrics/v1alpha1"
38	metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
39	metricsfake "k8s.io/metrics/pkg/client/clientset/versioned/fake"
40)
41
42const (
43	apiPrefix  = "api"
44	apiVersion = "v1"
45)
46
47func TestTopNodeAllMetrics(t *testing.T) {
48	cmdtesting.InitTestErrorHandler(t)
49	metrics, nodes := testNodeV1alpha1MetricsData()
50	expectedMetricsPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsAPIVersion)
51	expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
52
53	tf := cmdtesting.NewTestFactory().WithNamespace("test")
54	defer tf.Cleanup()
55
56	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
57	ns := scheme.Codecs.WithoutConversion()
58
59	tf.Client = &fake.RESTClient{
60		NegotiatedSerializer: ns,
61		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
62			switch p, m := req.URL.Path, req.Method; {
63			case p == "/api":
64				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
65			case p == "/apis":
66				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
67			case p == expectedMetricsPath && m == "GET":
68				body, err := marshallBody(metrics)
69				if err != nil {
70					t.Errorf("unexpected error: %v", err)
71				}
72				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
73			case p == expectedNodePath && m == "GET":
74				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, nodes)}, nil
75			default:
76				t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedMetricsPath)
77				return nil, nil
78			}
79		}),
80	}
81	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
82	streams, _, buf, _ := genericclioptions.NewTestIOStreams()
83
84	cmd := NewCmdTopNode(tf, nil, streams)
85	cmd.Flags().Set("no-headers", "true")
86	cmd.Run(cmd, []string{})
87
88	// Check the presence of node names in the output.
89	result := buf.String()
90	for _, m := range metrics.Items {
91		if !strings.Contains(result, m.Name) {
92			t.Errorf("missing metrics for %s: \n%s", m.Name, result)
93		}
94	}
95	if strings.Contains(result, "MEMORY") {
96		t.Errorf("should not print headers with --no-headers option set:\n%s\n", result)
97	}
98}
99
100func TestTopNodeAllMetricsCustomDefaults(t *testing.T) {
101	customBaseHeapsterServiceAddress := "/api/v1/namespaces/custom-namespace/services/https:custom-heapster-service:/proxy"
102	customBaseMetricsAddress := customBaseHeapsterServiceAddress + "/apis/metrics"
103
104	cmdtesting.InitTestErrorHandler(t)
105	metrics, nodes := testNodeV1alpha1MetricsData()
106	expectedMetricsPath := fmt.Sprintf("%s/%s/nodes", customBaseMetricsAddress, metricsAPIVersion)
107	expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
108
109	tf := cmdtesting.NewTestFactory().WithNamespace("test")
110	defer tf.Cleanup()
111
112	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
113	ns := scheme.Codecs.WithoutConversion()
114
115	tf.Client = &fake.RESTClient{
116		NegotiatedSerializer: ns,
117		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
118			switch p, m := req.URL.Path, req.Method; {
119			case p == "/api":
120				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
121			case p == "/apis":
122				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
123			case p == expectedMetricsPath && m == "GET":
124				body, err := marshallBody(metrics)
125				if err != nil {
126					t.Errorf("unexpected error: %v", err)
127				}
128				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
129			case p == expectedNodePath && m == "GET":
130				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, nodes)}, nil
131			default:
132				t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedMetricsPath)
133				return nil, nil
134			}
135		}),
136	}
137	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
138	streams, _, buf, _ := genericclioptions.NewTestIOStreams()
139
140	opts := &TopNodeOptions{
141		HeapsterOptions: HeapsterTopOptions{
142			Namespace: "custom-namespace",
143			Scheme:    "https",
144			Service:   "custom-heapster-service",
145		},
146		IOStreams: streams,
147	}
148	cmd := NewCmdTopNode(tf, opts, streams)
149	cmd.Run(cmd, []string{})
150
151	// Check the presence of node names in the output.
152	result := buf.String()
153	for _, m := range metrics.Items {
154		if !strings.Contains(result, m.Name) {
155			t.Errorf("missing metrics for %s: \n%s", m.Name, result)
156		}
157	}
158}
159
160func TestTopNodeWithNameMetrics(t *testing.T) {
161	cmdtesting.InitTestErrorHandler(t)
162	metrics, nodes := testNodeV1alpha1MetricsData()
163	expectedMetrics := metrics.Items[0]
164	expectedNode := nodes.Items[0]
165	nonExpectedMetrics := metricsv1alpha1api.NodeMetricsList{
166		ListMeta: metrics.ListMeta,
167		Items:    metrics.Items[1:],
168	}
169	expectedPath := fmt.Sprintf("%s/%s/nodes/%s", baseMetricsAddress, metricsAPIVersion, expectedMetrics.Name)
170	expectedNodePath := fmt.Sprintf("/%s/%s/nodes/%s", apiPrefix, apiVersion, expectedMetrics.Name)
171
172	tf := cmdtesting.NewTestFactory().WithNamespace("test")
173	defer tf.Cleanup()
174
175	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
176	ns := scheme.Codecs.WithoutConversion()
177
178	tf.Client = &fake.RESTClient{
179		NegotiatedSerializer: ns,
180		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
181			switch p, m := req.URL.Path, req.Method; {
182			case p == "/api":
183				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
184			case p == "/apis":
185				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
186			case p == expectedPath && m == "GET":
187				body, err := marshallBody(expectedMetrics)
188				if err != nil {
189					t.Errorf("unexpected error: %v", err)
190				}
191				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
192			case p == expectedNodePath && m == "GET":
193				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &expectedNode)}, nil
194			default:
195				t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedPath)
196				return nil, nil
197			}
198		}),
199	}
200	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
201	streams, _, buf, _ := genericclioptions.NewTestIOStreams()
202
203	cmd := NewCmdTopNode(tf, nil, streams)
204	cmd.Run(cmd, []string{expectedMetrics.Name})
205
206	// Check the presence of node names in the output.
207	result := buf.String()
208	if !strings.Contains(result, expectedMetrics.Name) {
209		t.Errorf("missing metrics for %s: \n%s", expectedMetrics.Name, result)
210	}
211	for _, m := range nonExpectedMetrics.Items {
212		if strings.Contains(result, m.Name) {
213			t.Errorf("unexpected metrics for %s: \n%s", m.Name, result)
214		}
215	}
216}
217
218func TestTopNodeWithLabelSelectorMetrics(t *testing.T) {
219	cmdtesting.InitTestErrorHandler(t)
220	metrics, nodes := testNodeV1alpha1MetricsData()
221	expectedMetrics := metricsv1alpha1api.NodeMetricsList{
222		ListMeta: metrics.ListMeta,
223		Items:    metrics.Items[0:1],
224	}
225	expectedNodes := v1.NodeList{
226		ListMeta: nodes.ListMeta,
227		Items:    nodes.Items[0:1],
228	}
229	nonExpectedMetrics := metricsv1alpha1api.NodeMetricsList{
230		ListMeta: metrics.ListMeta,
231		Items:    metrics.Items[1:],
232	}
233	label := "key=value"
234	expectedPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsAPIVersion)
235	expectedQuery := fmt.Sprintf("labelSelector=%s", url.QueryEscape(label))
236	expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
237
238	tf := cmdtesting.NewTestFactory().WithNamespace("test")
239	defer tf.Cleanup()
240
241	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
242	ns := scheme.Codecs.WithoutConversion()
243
244	tf.Client = &fake.RESTClient{
245		NegotiatedSerializer: ns,
246		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
247			switch p, m, q := req.URL.Path, req.Method, req.URL.RawQuery; {
248			case p == "/api":
249				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
250			case p == "/apis":
251				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
252			case p == expectedPath && m == "GET" && q == expectedQuery:
253				body, err := marshallBody(expectedMetrics)
254				if err != nil {
255					t.Errorf("unexpected error: %v", err)
256				}
257				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
258			case p == expectedNodePath && m == "GET":
259				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &expectedNodes)}, nil
260			default:
261				t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedPath)
262				return nil, nil
263			}
264		}),
265	}
266	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
267	streams, _, buf, _ := genericclioptions.NewTestIOStreams()
268
269	cmd := NewCmdTopNode(tf, nil, streams)
270	cmd.Flags().Set("selector", label)
271	cmd.Run(cmd, []string{})
272
273	// Check the presence of node names in the output.
274	result := buf.String()
275	for _, m := range expectedMetrics.Items {
276		if !strings.Contains(result, m.Name) {
277			t.Errorf("missing metrics for %s: \n%s", m.Name, result)
278		}
279	}
280	for _, m := range nonExpectedMetrics.Items {
281		if strings.Contains(result, m.Name) {
282			t.Errorf("unexpected metrics for %s: \n%s", m.Name, result)
283		}
284	}
285}
286
287func TestTopNodeWithSortByCpuMetrics(t *testing.T) {
288	cmdtesting.InitTestErrorHandler(t)
289	metrics, nodes := testNodeV1beta1MetricsData()
290	expectedMetrics := metricsv1beta1api.NodeMetricsList{
291		ListMeta: metrics.ListMeta,
292		Items:    metrics.Items[:],
293	}
294
295	expectedMetricsPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsAPIVersion)
296	expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
297	expectedNodes := []string{"node2", "node3", "node1"}
298
299	tf := cmdtesting.NewTestFactory().WithNamespace("test")
300	defer tf.Cleanup()
301
302	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
303	ns := scheme.Codecs
304
305	tf.Client = &fake.RESTClient{
306		NegotiatedSerializer: ns,
307		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
308			switch p, m := req.URL.Path, req.Method; {
309			case p == "/api":
310				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
311			case p == "/apis":
312				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
313			case p == expectedMetricsPath && m == "GET":
314				body, err := marshallBody(expectedMetrics)
315				if err != nil {
316					t.Errorf("unexpected error: %v", err)
317				}
318				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
319			case p == expectedNodePath && m == "GET":
320				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, nodes)}, nil
321			default:
322				t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedMetricsPath)
323				return nil, nil
324			}
325		}),
326	}
327	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
328	streams, _, buf, _ := genericclioptions.NewTestIOStreams()
329
330	cmd := NewCmdTopNode(tf, nil, streams)
331	cmd.Flags().Set("sort-by", "cpu")
332
333	cmd.Run(cmd, []string{})
334
335	// Check the presence of node names in the output.
336	result := buf.String()
337
338	for _, m := range expectedMetrics.Items {
339		if !strings.Contains(result, m.Name) {
340			t.Errorf("missing metrics for %s: \n%s", m.Name, result)
341		}
342	}
343
344	resultLines := strings.Split(result, "\n")
345	resultNodes := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line
346
347	for i, line := range resultLines[1 : len(resultLines)-1] { // don't process first (header) and last (empty) line
348		lineFirstColumn := strings.Split(line, " ")[0]
349		resultNodes[i] = lineFirstColumn
350	}
351
352	if !reflect.DeepEqual(resultNodes, expectedNodes) {
353		t.Errorf("kinds not matching:\n\texpectedKinds: %v\n\tgotKinds: %v\n", expectedNodes, resultNodes)
354	}
355
356}
357
358func TestTopNodeWithSortByMemoryMetrics(t *testing.T) {
359	cmdtesting.InitTestErrorHandler(t)
360	metrics, nodes := testNodeV1beta1MetricsData()
361	expectedMetrics := metricsv1beta1api.NodeMetricsList{
362		ListMeta: metrics.ListMeta,
363		Items:    metrics.Items[:],
364	}
365	expectedMetricsPath := fmt.Sprintf("%s/%s/nodes", baseMetricsAddress, metricsAPIVersion)
366	expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
367	expectedNodes := []string{"node2", "node3", "node1"}
368
369	tf := cmdtesting.NewTestFactory().WithNamespace("test")
370	defer tf.Cleanup()
371
372	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
373	ns := scheme.Codecs
374
375	tf.Client = &fake.RESTClient{
376		NegotiatedSerializer: ns,
377		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
378			switch p, m := req.URL.Path, req.Method; {
379			case p == "/api":
380				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
381			case p == "/apis":
382				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbody)))}, nil
383			case p == expectedMetricsPath && m == "GET":
384				body, err := marshallBody(expectedMetrics)
385				if err != nil {
386					t.Errorf("unexpected error: %v", err)
387				}
388				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
389			case p == expectedNodePath && m == "GET":
390				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, nodes)}, nil
391			default:
392				t.Fatalf("unexpected request: %#v\nGot URL: %#v\nExpected path: %#v", req, req.URL, expectedMetricsPath)
393				return nil, nil
394			}
395		}),
396	}
397	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
398	streams, _, buf, _ := genericclioptions.NewTestIOStreams()
399
400	cmd := NewCmdTopNode(tf, nil, streams)
401	cmd.Flags().Set("sort-by", "memory")
402
403	cmd.Run(cmd, []string{})
404
405	// Check the presence of node names in the output.
406	result := buf.String()
407
408	for _, m := range expectedMetrics.Items {
409		if !strings.Contains(result, m.Name) {
410			t.Errorf("missing metrics for %s: \n%s", m.Name, result)
411		}
412	}
413
414	resultLines := strings.Split(result, "\n")
415	resultNodes := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line
416
417	for i, line := range resultLines[1 : len(resultLines)-1] { // don't process first (header) and last (empty) line
418		lineFirstColumn := strings.Split(line, " ")[0]
419		resultNodes[i] = lineFirstColumn
420	}
421
422	if !reflect.DeepEqual(resultNodes, expectedNodes) {
423		t.Errorf("kinds not matching:\n\texpectedKinds: %v\n\tgotKinds: %v\n", expectedNodes, resultNodes)
424	}
425
426}
427
428func TestTopNodeAllMetricsFromMetricsServer(t *testing.T) {
429	cmdtesting.InitTestErrorHandler(t)
430	expectedMetrics, nodes := testNodeV1beta1MetricsData()
431	expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
432
433	tf := cmdtesting.NewTestFactory().WithNamespace("test")
434	defer tf.Cleanup()
435
436	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
437	ns := scheme.Codecs.WithoutConversion()
438
439	tf.Client = &fake.RESTClient{
440		NegotiatedSerializer: ns,
441		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
442			switch p, m := req.URL.Path, req.Method; {
443			case p == "/api":
444				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
445			case p == "/apis":
446				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbodyWithMetrics)))}, nil
447			case p == expectedNodePath && m == "GET":
448				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, nodes)}, nil
449			default:
450				t.Fatalf("unexpected request: %#v\nGot URL: %#v\n", req, req.URL)
451				return nil, nil
452			}
453		}),
454	}
455	fakemetricsClientset := &metricsfake.Clientset{}
456	fakemetricsClientset.AddReactor("list", "nodes", func(action core.Action) (handled bool, ret runtime.Object, err error) {
457		return true, expectedMetrics, nil
458	})
459	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
460	streams, _, buf, _ := genericclioptions.NewTestIOStreams()
461
462	cmd := NewCmdTopNode(tf, nil, streams)
463
464	// TODO in the long run, we want to test most of our commands like this. Wire the options struct with specific mocks
465	// TODO then check the particular Run functionality and harvest results from fake clients
466	cmdOptions := &TopNodeOptions{
467		IOStreams: streams,
468	}
469	if err := cmdOptions.Complete(tf, cmd, []string{}); err != nil {
470		t.Fatal(err)
471	}
472	cmdOptions.MetricsClient = fakemetricsClientset
473	if err := cmdOptions.Validate(); err != nil {
474		t.Fatal(err)
475	}
476	if err := cmdOptions.RunTopNode(); err != nil {
477		t.Fatal(err)
478	}
479
480	// Check the presence of node names in the output.
481	result := buf.String()
482	for _, m := range expectedMetrics.Items {
483		if !strings.Contains(result, m.Name) {
484			t.Errorf("missing metrics for %s: \n%s", m.Name, result)
485		}
486	}
487}
488
489func TestTopNodeWithNameMetricsFromMetricsServer(t *testing.T) {
490	cmdtesting.InitTestErrorHandler(t)
491	metrics, nodes := testNodeV1beta1MetricsData()
492	expectedMetrics := metrics.Items[0]
493	expectedNode := nodes.Items[0]
494	nonExpectedMetrics := metricsv1beta1api.NodeMetricsList{
495		ListMeta: metrics.ListMeta,
496		Items:    metrics.Items[1:],
497	}
498	expectedNodePath := fmt.Sprintf("/%s/%s/nodes/%s", apiPrefix, apiVersion, expectedMetrics.Name)
499
500	tf := cmdtesting.NewTestFactory().WithNamespace("test")
501	defer tf.Cleanup()
502
503	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
504	ns := scheme.Codecs.WithoutConversion()
505
506	tf.Client = &fake.RESTClient{
507		NegotiatedSerializer: ns,
508		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
509			switch p, m := req.URL.Path, req.Method; {
510			case p == "/api":
511				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
512			case p == "/apis":
513				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbodyWithMetrics)))}, nil
514			case p == expectedNodePath && m == "GET":
515				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &expectedNode)}, nil
516			default:
517				t.Fatalf("unexpected request: %#v\nGot URL: %#v\n", req, req.URL)
518				return nil, nil
519			}
520		}),
521	}
522	fakemetricsClientset := &metricsfake.Clientset{}
523	fakemetricsClientset.AddReactor("get", "nodes", func(action core.Action) (handled bool, ret runtime.Object, err error) {
524		return true, &expectedMetrics, nil
525	})
526	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
527	streams, _, buf, _ := genericclioptions.NewTestIOStreams()
528
529	cmd := NewCmdTopNode(tf, nil, streams)
530
531	// TODO in the long run, we want to test most of our commands like this. Wire the options struct with specific mocks
532	// TODO then check the particular Run functionality and harvest results from fake clients
533	cmdOptions := &TopNodeOptions{
534		IOStreams: streams,
535	}
536	if err := cmdOptions.Complete(tf, cmd, []string{expectedMetrics.Name}); err != nil {
537		t.Fatal(err)
538	}
539	cmdOptions.MetricsClient = fakemetricsClientset
540	if err := cmdOptions.Validate(); err != nil {
541		t.Fatal(err)
542	}
543	if err := cmdOptions.RunTopNode(); err != nil {
544		t.Fatal(err)
545	}
546
547	// Check the presence of node names in the output.
548	result := buf.String()
549	if !strings.Contains(result, expectedMetrics.Name) {
550		t.Errorf("missing metrics for %s: \n%s", expectedMetrics.Name, result)
551	}
552	for _, m := range nonExpectedMetrics.Items {
553		if strings.Contains(result, m.Name) {
554			t.Errorf("unexpected metrics for %s: \n%s", m.Name, result)
555		}
556	}
557}
558
559func TestTopNodeWithLabelSelectorMetricsFromMetricsServer(t *testing.T) {
560	cmdtesting.InitTestErrorHandler(t)
561	metrics, nodes := testNodeV1beta1MetricsData()
562	expectedMetrics := &metricsv1beta1api.NodeMetricsList{
563		ListMeta: metrics.ListMeta,
564		Items:    metrics.Items[0:1],
565	}
566	expectedNodes := v1.NodeList{
567		ListMeta: nodes.ListMeta,
568		Items:    nodes.Items[0:1],
569	}
570	nonExpectedMetrics := &metricsv1beta1api.NodeMetricsList{
571		ListMeta: metrics.ListMeta,
572		Items:    metrics.Items[1:],
573	}
574	label := "key=value"
575	expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
576
577	tf := cmdtesting.NewTestFactory().WithNamespace("test")
578	defer tf.Cleanup()
579
580	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
581	ns := scheme.Codecs.WithoutConversion()
582
583	tf.Client = &fake.RESTClient{
584		NegotiatedSerializer: ns,
585		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
586			switch p, m, _ := req.URL.Path, req.Method, req.URL.RawQuery; {
587			case p == "/api":
588				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
589			case p == "/apis":
590				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbodyWithMetrics)))}, nil
591			case p == expectedNodePath && m == "GET":
592				return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &expectedNodes)}, nil
593			default:
594				t.Fatalf("unexpected request: %#v\nGot URL: %#v\n", req, req.URL)
595				return nil, nil
596			}
597		}),
598	}
599
600	fakemetricsClientset := &metricsfake.Clientset{}
601	fakemetricsClientset.AddReactor("list", "nodes", func(action core.Action) (handled bool, ret runtime.Object, err error) {
602		return true, expectedMetrics, nil
603	})
604	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
605	streams, _, buf, _ := genericclioptions.NewTestIOStreams()
606
607	cmd := NewCmdTopNode(tf, nil, streams)
608	cmd.Flags().Set("selector", label)
609
610	// TODO in the long run, we want to test most of our commands like this. Wire the options struct with specific mocks
611	// TODO then check the particular Run functionality and harvest results from fake clients
612	cmdOptions := &TopNodeOptions{
613		IOStreams: streams,
614	}
615	if err := cmdOptions.Complete(tf, cmd, []string{}); err != nil {
616		t.Fatal(err)
617	}
618	cmdOptions.MetricsClient = fakemetricsClientset
619	if err := cmdOptions.Validate(); err != nil {
620		t.Fatal(err)
621	}
622	if err := cmdOptions.RunTopNode(); err != nil {
623		t.Fatal(err)
624	}
625
626	// Check the presence of node names in the output.
627	result := buf.String()
628	for _, m := range expectedMetrics.Items {
629		if !strings.Contains(result, m.Name) {
630			t.Errorf("missing metrics for %s: \n%s", m.Name, result)
631		}
632	}
633	for _, m := range nonExpectedMetrics.Items {
634		if strings.Contains(result, m.Name) {
635			t.Errorf("unexpected metrics for %s: \n%s", m.Name, result)
636		}
637	}
638}
639
640func TestTopNodeWithSortByCpuMetricsFromMetricsServer(t *testing.T) {
641	cmdtesting.InitTestErrorHandler(t)
642	metrics, nodes := testNodeV1beta1MetricsData()
643	expectedMetrics := &metricsv1beta1api.NodeMetricsList{
644		ListMeta: metrics.ListMeta,
645		Items:    metrics.Items[:],
646	}
647	expectedNodes := v1.NodeList{
648		ListMeta: nodes.ListMeta,
649		Items:    nodes.Items[:],
650	}
651	expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
652	expectedNodesNames := []string{"node2", "node3", "node1"}
653
654	tf := cmdtesting.NewTestFactory().WithNamespace("test")
655	defer tf.Cleanup()
656
657	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
658	ns := scheme.Codecs
659
660	tf.Client = &fake.RESTClient{
661		NegotiatedSerializer: ns,
662		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
663			switch p, m := req.URL.Path, req.Method; {
664			case p == "/api":
665				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
666			case p == "/apis":
667				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbodyWithMetrics)))}, nil
668			case p == expectedNodePath && m == "GET":
669				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &expectedNodes)}, nil
670			default:
671				t.Fatalf("unexpected request: %#v\nGot URL: %#v\n", req, req.URL)
672				return nil, nil
673			}
674		}),
675	}
676	fakemetricsClientset := &metricsfake.Clientset{}
677	fakemetricsClientset.AddReactor("list", "nodes", func(action core.Action) (handled bool, ret runtime.Object, err error) {
678		return true, expectedMetrics, nil
679	})
680	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
681	streams, _, buf, _ := genericclioptions.NewTestIOStreams()
682
683	cmd := NewCmdTopNode(tf, nil, streams)
684	cmd.Flags().Set("sort-by", "cpu")
685
686	// TODO in the long run, we want to test most of our commands like this. Wire the options struct with specific mocks
687	// TODO then check the particular Run functionality and harvest results from fake clients
688	cmdOptions := &TopNodeOptions{
689		IOStreams: streams,
690		SortBy:    "cpu",
691	}
692	if err := cmdOptions.Complete(tf, cmd, []string{}); err != nil {
693		t.Fatal(err)
694	}
695	cmdOptions.MetricsClient = fakemetricsClientset
696	if err := cmdOptions.Validate(); err != nil {
697		t.Fatal(err)
698	}
699	if err := cmdOptions.RunTopNode(); err != nil {
700		t.Fatal(err)
701	}
702
703	// Check the presence of node names in the output.
704	result := buf.String()
705
706	for _, m := range expectedMetrics.Items {
707		if !strings.Contains(result, m.Name) {
708			t.Errorf("missing metrics for %s: \n%s", m.Name, result)
709		}
710	}
711
712	resultLines := strings.Split(result, "\n")
713	resultNodes := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line
714
715	for i, line := range resultLines[1 : len(resultLines)-1] { // don't process first (header) and last (empty) line
716		lineFirstColumn := strings.Split(line, " ")[0]
717		resultNodes[i] = lineFirstColumn
718	}
719
720	if !reflect.DeepEqual(resultNodes, expectedNodesNames) {
721		t.Errorf("kinds not matching:\n\texpectedKinds: %v\n\tgotKinds: %v\n", expectedNodesNames, resultNodes)
722	}
723
724}
725
726func TestTopNodeWithSortByMemoryMetricsFromMetricsServer(t *testing.T) {
727	cmdtesting.InitTestErrorHandler(t)
728	metrics, nodes := testNodeV1beta1MetricsData()
729	expectedMetrics := &metricsv1beta1api.NodeMetricsList{
730		ListMeta: metrics.ListMeta,
731		Items:    metrics.Items[:],
732	}
733	expectedNodes := v1.NodeList{
734		ListMeta: nodes.ListMeta,
735		Items:    nodes.Items[:],
736	}
737	expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
738	expectedNodesNames := []string{"node2", "node3", "node1"}
739
740	tf := cmdtesting.NewTestFactory().WithNamespace("test")
741	defer tf.Cleanup()
742
743	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
744	ns := scheme.Codecs
745
746	tf.Client = &fake.RESTClient{
747		NegotiatedSerializer: ns,
748		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
749			switch p, m := req.URL.Path, req.Method; {
750			case p == "/api":
751				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
752			case p == "/apis":
753				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbodyWithMetrics)))}, nil
754			case p == expectedNodePath && m == "GET":
755				return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &expectedNodes)}, nil
756			default:
757				t.Fatalf("unexpected request: %#v\nGot URL: %#v\n", req, req.URL)
758				return nil, nil
759			}
760		}),
761	}
762	fakemetricsClientset := &metricsfake.Clientset{}
763	fakemetricsClientset.AddReactor("list", "nodes", func(action core.Action) (handled bool, ret runtime.Object, err error) {
764		return true, expectedMetrics, nil
765	})
766	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
767	streams, _, buf, _ := genericclioptions.NewTestIOStreams()
768
769	cmd := NewCmdTopNode(tf, nil, streams)
770	cmd.Flags().Set("sort-by", "memory")
771
772	// TODO in the long run, we want to test most of our commands like this. Wire the options struct with specific mocks
773	// TODO then check the particular Run functionality and harvest results from fake clients
774	cmdOptions := &TopNodeOptions{
775		IOStreams: streams,
776		SortBy:    "memory",
777	}
778	if err := cmdOptions.Complete(tf, cmd, []string{}); err != nil {
779		t.Fatal(err)
780	}
781	cmdOptions.MetricsClient = fakemetricsClientset
782	if err := cmdOptions.Validate(); err != nil {
783		t.Fatal(err)
784	}
785	if err := cmdOptions.RunTopNode(); err != nil {
786		t.Fatal(err)
787	}
788
789	// Check the presence of node names in the output.
790	result := buf.String()
791
792	for _, m := range expectedMetrics.Items {
793		if !strings.Contains(result, m.Name) {
794			t.Errorf("missing metrics for %s: \n%s", m.Name, result)
795		}
796	}
797
798	resultLines := strings.Split(result, "\n")
799	resultNodes := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line
800
801	for i, line := range resultLines[1 : len(resultLines)-1] { // don't process first (header) and last (empty) line
802		lineFirstColumn := strings.Split(line, " ")[0]
803		resultNodes[i] = lineFirstColumn
804	}
805
806	if !reflect.DeepEqual(resultNodes, expectedNodesNames) {
807		t.Errorf("kinds not matching:\n\texpectedKinds: %v\n\tgotKinds: %v\n", expectedNodesNames, resultNodes)
808	}
809
810}
811