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