k8s.io/kubernetes@v1.29.3/pkg/printers/internalversion/printers_test.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package internalversion 18 19 import ( 20 "fmt" 21 "math" 22 "reflect" 23 "runtime" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 apiv1 "k8s.io/api/core/v1" 29 "k8s.io/apimachinery/pkg/api/resource" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/apimachinery/pkg/util/intstr" 33 "k8s.io/client-go/util/certificate/csr" 34 "k8s.io/kubernetes/pkg/apis/admissionregistration" 35 "k8s.io/kubernetes/pkg/apis/apiserverinternal" 36 "k8s.io/kubernetes/pkg/apis/apps" 37 "k8s.io/kubernetes/pkg/apis/autoscaling" 38 "k8s.io/kubernetes/pkg/apis/batch" 39 "k8s.io/kubernetes/pkg/apis/certificates" 40 "k8s.io/kubernetes/pkg/apis/coordination" 41 api "k8s.io/kubernetes/pkg/apis/core" 42 "k8s.io/kubernetes/pkg/apis/discovery" 43 "k8s.io/kubernetes/pkg/apis/flowcontrol" 44 "k8s.io/kubernetes/pkg/apis/networking" 45 nodeapi "k8s.io/kubernetes/pkg/apis/node" 46 "k8s.io/kubernetes/pkg/apis/policy" 47 "k8s.io/kubernetes/pkg/apis/rbac" 48 "k8s.io/kubernetes/pkg/apis/scheduling" 49 "k8s.io/kubernetes/pkg/apis/storage" 50 "k8s.io/kubernetes/pkg/printers" 51 utilpointer "k8s.io/utils/pointer" 52 ) 53 54 var containerRestartPolicyAlways = api.ContainerRestartPolicyAlways 55 56 func TestFormatResourceName(t *testing.T) { 57 tests := []struct { 58 kind schema.GroupKind 59 name string 60 want string 61 }{ 62 {schema.GroupKind{}, "", ""}, 63 {schema.GroupKind{}, "name", "name"}, 64 {schema.GroupKind{Kind: "Kind"}, "", "kind/"}, // should not happen in practice 65 {schema.GroupKind{Kind: "Kind"}, "name", "kind/name"}, 66 {schema.GroupKind{Group: "group", Kind: "Kind"}, "name", "kind.group/name"}, 67 } 68 for _, tt := range tests { 69 if got := formatResourceName(tt.kind, tt.name, true); got != tt.want { 70 t.Errorf("formatResourceName(%q, %q) = %q, want %q", tt.kind, tt.name, got, tt.want) 71 } 72 } 73 } 74 75 type TestPrintHandler struct { 76 numCalls int 77 } 78 79 func (t *TestPrintHandler) TableHandler(columnDefinitions []metav1.TableColumnDefinition, printFunc interface{}) error { 80 t.numCalls++ 81 return nil 82 } 83 84 func (t *TestPrintHandler) getNumCalls() int { 85 return t.numCalls 86 } 87 88 func TestAllHandlers(t *testing.T) { 89 h := &TestPrintHandler{numCalls: 0} 90 AddHandlers(h) 91 if h.getNumCalls() == 0 { 92 t.Error("TableHandler not called in AddHandlers") 93 } 94 } 95 96 func TestPrintEvent(t *testing.T) { 97 tests := []struct { 98 event api.Event 99 options printers.GenerateOptions 100 expected []metav1.TableRow 101 }{ 102 // Basic event; no generate options 103 { 104 event: api.Event{ 105 Source: api.EventSource{Component: "kubelet"}, 106 InvolvedObject: api.ObjectReference{ 107 Kind: "Pod", 108 Name: "Pod Name", 109 FieldPath: "spec.containers{foo}", 110 }, 111 Reason: "Event Reason", 112 Message: "Message Data", 113 FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)}, 114 LastTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}, 115 Count: 6, 116 Type: api.EventTypeNormal, 117 ObjectMeta: metav1.ObjectMeta{Name: "event1"}, 118 }, 119 options: printers.GenerateOptions{}, 120 // Columns: Last Seen, Type, Reason, Object, Message 121 expected: []metav1.TableRow{{Cells: []interface{}{"2d", "Normal", "Event Reason", "pod/Pod Name", "Message Data"}}}, 122 }, 123 // Basic event; generate options=Wide 124 { 125 event: api.Event{ 126 Source: api.EventSource{ 127 Component: "kubelet", 128 Host: "Node1", 129 }, 130 InvolvedObject: api.ObjectReference{ 131 Kind: "Deployment", 132 Name: "Deployment Name", 133 FieldPath: "spec.containers{foo}", 134 }, 135 Reason: "Event Reason", 136 Message: "Message Data", 137 FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)}, 138 LastTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}, 139 Count: 6, 140 Type: api.EventTypeWarning, 141 ObjectMeta: metav1.ObjectMeta{Name: "event2"}, 142 }, 143 options: printers.GenerateOptions{Wide: true}, 144 // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name 145 expected: []metav1.TableRow{{Cells: []interface{}{"2d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(6), "event2"}}}, 146 }, 147 // Basic event, w/o FirstTimestamp set 148 { 149 event: api.Event{ 150 Source: api.EventSource{ 151 Component: "kubelet", 152 Host: "Node1", 153 }, 154 InvolvedObject: api.ObjectReference{ 155 Kind: "Deployment", 156 Name: "Deployment Name", 157 FieldPath: "spec.containers{foo}", 158 }, 159 Reason: "Event Reason", 160 Message: "Message Data", 161 EventTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)}, 162 LastTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)}, 163 Count: 1, 164 Type: api.EventTypeWarning, 165 ObjectMeta: metav1.ObjectMeta{Name: "event3"}, 166 }, 167 options: printers.GenerateOptions{Wide: true}, 168 // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name 169 expected: []metav1.TableRow{{Cells: []interface{}{"3d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(1), "event3"}}}, 170 }, 171 // Basic event, w/o LastTimestamp set 172 { 173 event: api.Event{ 174 Source: api.EventSource{ 175 Component: "kubelet", 176 Host: "Node1", 177 }, 178 InvolvedObject: api.ObjectReference{ 179 Kind: "Deployment", 180 Name: "Deployment Name", 181 FieldPath: "spec.containers{foo}", 182 }, 183 Reason: "Event Reason", 184 Message: "Message Data", 185 EventTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)}, 186 FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)}, 187 Count: 1, 188 Type: api.EventTypeWarning, 189 ObjectMeta: metav1.ObjectMeta{Name: "event4"}, 190 }, 191 options: printers.GenerateOptions{Wide: true}, 192 // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name 193 expected: []metav1.TableRow{{Cells: []interface{}{"3d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(1), "event4"}}}, 194 }, 195 // Basic event, w/o FirstTimestamp and LastTimestamp set 196 { 197 event: api.Event{ 198 Source: api.EventSource{ 199 Component: "kubelet", 200 Host: "Node1", 201 }, 202 InvolvedObject: api.ObjectReference{ 203 Kind: "Deployment", 204 Name: "Deployment Name", 205 FieldPath: "spec.containers{foo}", 206 }, 207 Reason: "Event Reason", 208 Message: "Message Data", 209 EventTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)}, 210 Count: 1, 211 Type: api.EventTypeWarning, 212 ObjectMeta: metav1.ObjectMeta{Name: "event5"}, 213 }, 214 options: printers.GenerateOptions{Wide: true}, 215 // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name 216 expected: []metav1.TableRow{{Cells: []interface{}{"3d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(1), "event5"}}}, 217 }, 218 // Basic event serie, w/o FirstTimestamp, LastTimestamp and Count set 219 { 220 event: api.Event{ 221 Source: api.EventSource{ 222 Component: "kubelet", 223 Host: "Node1", 224 }, 225 InvolvedObject: api.ObjectReference{ 226 Kind: "Deployment", 227 Name: "Deployment Name", 228 FieldPath: "spec.containers{foo}", 229 }, 230 Series: &api.EventSeries{ 231 Count: 2, 232 LastObservedTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -2)}, 233 }, 234 Reason: "Event Reason", 235 Message: "Message Data", 236 EventTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)}, 237 Type: api.EventTypeWarning, 238 ObjectMeta: metav1.ObjectMeta{Name: "event6"}, 239 }, 240 options: printers.GenerateOptions{Wide: true}, 241 // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name 242 expected: []metav1.TableRow{{Cells: []interface{}{"2d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(2), "event6"}}}, 243 }, 244 // Singleton event, w/o FirstTimestamp, LastTimestamp and Count set 245 { 246 event: api.Event{ 247 Source: api.EventSource{ 248 Component: "kubelet", 249 Host: "Node1", 250 }, 251 InvolvedObject: api.ObjectReference{ 252 Kind: "Deployment", 253 Name: "Deployment Name", 254 FieldPath: "spec.containers{foo}", 255 }, 256 Reason: "Event Reason", 257 Message: "Message Data", 258 EventTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)}, 259 Type: api.EventTypeWarning, 260 ObjectMeta: metav1.ObjectMeta{Name: "event7"}, 261 }, 262 options: printers.GenerateOptions{Wide: true}, 263 // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name 264 expected: []metav1.TableRow{{Cells: []interface{}{"3d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(1), "event7"}}}, 265 }, 266 // Basic event, with empty Source; generate options=Wide 267 { 268 event: api.Event{ 269 ReportingController: "kubelet", 270 ReportingInstance: "test", 271 InvolvedObject: api.ObjectReference{ 272 Kind: "Deployment", 273 Name: "Deployment Name", 274 FieldPath: "spec.containers{foo}", 275 }, 276 Reason: "Event Reason", 277 Message: "Message Data", 278 FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)}, 279 LastTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}, 280 Count: 6, 281 Type: api.EventTypeWarning, 282 ObjectMeta: metav1.ObjectMeta{Name: "event2"}, 283 }, 284 options: printers.GenerateOptions{Wide: true}, 285 // Columns: Last Seen, Type, Reason, Object, Subobject, Source, Message, First Seen, Count, Name 286 expected: []metav1.TableRow{{Cells: []interface{}{"2d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, test", "Message Data", "3d", int64(6), "event2"}}}, 287 }, 288 } 289 290 for i, test := range tests { 291 rows, err := printEvent(&test.event, test.options) 292 if err != nil { 293 t.Fatal(err) 294 } 295 for i := range rows { 296 rows[i].Object.Object = nil 297 } 298 if !reflect.DeepEqual(test.expected, rows) { 299 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 300 } 301 } 302 } 303 304 func TestPrintEventsResultSorted(t *testing.T) { 305 306 eventList := api.EventList{ 307 Items: []api.Event{ 308 { 309 Source: api.EventSource{Component: "kubelet"}, 310 Message: "Item 1", 311 FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), 312 LastTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), 313 Count: 1, 314 Type: api.EventTypeNormal, 315 }, 316 { 317 Source: api.EventSource{Component: "scheduler"}, 318 Message: "Item 2", 319 FirstTimestamp: metav1.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)), 320 LastTimestamp: metav1.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)), 321 Count: 1, 322 Type: api.EventTypeNormal, 323 }, 324 { 325 Source: api.EventSource{Component: "kubelet"}, 326 Message: "Item 3", 327 FirstTimestamp: metav1.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)), 328 LastTimestamp: metav1.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)), 329 Count: 1, 330 Type: api.EventTypeNormal, 331 }, 332 }, 333 } 334 335 rows, err := printEventList(&eventList, printers.GenerateOptions{}) 336 if err != nil { 337 t.Fatal(err) 338 } 339 if len(rows) != 3 { 340 t.Errorf("Generate Event List: Wrong number of table rows returned. Expected 3, got (%d)", len(rows)) 341 } 342 // Verify the watch event dates are in order. 343 firstRow := rows[0] 344 message1 := firstRow.Cells[4] 345 if message1.(string) != "Item 1" { 346 t.Errorf("Wrong event ordering: expecting (Item 1), got (%s)", message1) 347 } 348 secondRow := rows[1] 349 message2 := secondRow.Cells[4] 350 if message2 != "Item 2" { 351 t.Errorf("Wrong event ordering: expecting (Item 2), got (%s)", message2) 352 } 353 thirdRow := rows[2] 354 message3 := thirdRow.Cells[4] 355 if message3 != "Item 3" { 356 t.Errorf("Wrong event ordering: expecting (Item 3), got (%s)", message3) 357 } 358 } 359 360 func TestPrintNamespace(t *testing.T) { 361 tests := []struct { 362 namespace api.Namespace 363 expected []metav1.TableRow 364 }{ 365 // Basic namespace with status and age. 366 { 367 namespace: api.Namespace{ 368 ObjectMeta: metav1.ObjectMeta{ 369 Name: "namespace1", 370 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 371 }, 372 Status: api.NamespaceStatus{ 373 Phase: "FooStatus", 374 }, 375 }, 376 // Columns: Name, Status, Age 377 expected: []metav1.TableRow{{Cells: []interface{}{"namespace1", "FooStatus", "0s"}}}, 378 }, 379 // Basic namespace without status or age. 380 { 381 namespace: api.Namespace{ 382 ObjectMeta: metav1.ObjectMeta{ 383 Name: "namespace2", 384 }, 385 }, 386 // Columns: Name, Status, Age 387 expected: []metav1.TableRow{{Cells: []interface{}{"namespace2", "", "<unknown>"}}}, 388 }, 389 } 390 391 for i, test := range tests { 392 rows, err := printNamespace(&test.namespace, printers.GenerateOptions{}) 393 if err != nil { 394 t.Fatal(err) 395 } 396 for i := range rows { 397 rows[i].Object.Object = nil 398 } 399 if !reflect.DeepEqual(test.expected, rows) { 400 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 401 } 402 } 403 } 404 405 func TestPrintSecret(t *testing.T) { 406 tests := []struct { 407 secret api.Secret 408 expected []metav1.TableRow 409 }{ 410 // Basic namespace with type, data, and age. 411 { 412 secret: api.Secret{ 413 ObjectMeta: metav1.ObjectMeta{ 414 Name: "secret1", 415 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 416 }, 417 Type: "kubernetes.io/service-account-token", 418 Data: map[string][]byte{ 419 "token": []byte("secret data"), 420 }, 421 }, 422 // Columns: Name, Type, Data, Age 423 expected: []metav1.TableRow{{Cells: []interface{}{"secret1", "kubernetes.io/service-account-token", int64(1), "0s"}}}, 424 }, 425 // Basic namespace with type and age; no data. 426 { 427 secret: api.Secret{ 428 ObjectMeta: metav1.ObjectMeta{ 429 Name: "secret1", 430 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 431 }, 432 Type: "kubernetes.io/service-account-token", 433 }, 434 // Columns: Name, Type, Data, Age 435 expected: []metav1.TableRow{{Cells: []interface{}{"secret1", "kubernetes.io/service-account-token", int64(0), "0s"}}}, 436 }, 437 } 438 439 for i, test := range tests { 440 rows, err := printSecret(&test.secret, printers.GenerateOptions{}) 441 if err != nil { 442 t.Fatal(err) 443 } 444 for i := range rows { 445 rows[i].Object.Object = nil 446 } 447 if !reflect.DeepEqual(test.expected, rows) { 448 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 449 } 450 } 451 } 452 453 func TestPrintServiceAccount(t *testing.T) { 454 tests := []struct { 455 serviceAccount api.ServiceAccount 456 expected []metav1.TableRow 457 }{ 458 // Basic service account without secrets 459 { 460 serviceAccount: api.ServiceAccount{ 461 ObjectMeta: metav1.ObjectMeta{ 462 Name: "sa1", 463 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 464 }, 465 Secrets: []api.ObjectReference{}, 466 }, 467 // Columns: Name, (Num) Secrets, Age 468 expected: []metav1.TableRow{{Cells: []interface{}{"sa1", int64(0), "0s"}}}, 469 }, 470 // Basic service account with two secrets. 471 { 472 serviceAccount: api.ServiceAccount{ 473 ObjectMeta: metav1.ObjectMeta{ 474 Name: "sa1", 475 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 476 }, 477 Secrets: []api.ObjectReference{ 478 {Name: "Secret1"}, 479 {Name: "Secret2"}, 480 }, 481 }, 482 // Columns: Name, (Num) Secrets, Age 483 expected: []metav1.TableRow{{Cells: []interface{}{"sa1", int64(2), "0s"}}}, 484 }, 485 } 486 487 for i, test := range tests { 488 rows, err := printServiceAccount(&test.serviceAccount, printers.GenerateOptions{}) 489 if err != nil { 490 t.Fatal(err) 491 } 492 for i := range rows { 493 rows[i].Object.Object = nil 494 } 495 if !reflect.DeepEqual(test.expected, rows) { 496 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 497 } 498 } 499 } 500 501 func TestPrintNodeStatus(t *testing.T) { 502 503 table := []struct { 504 node api.Node 505 expected []metav1.TableRow 506 }{ 507 { 508 node: api.Node{ 509 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 510 Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionTrue}}}, 511 }, 512 // Columns: Name, Status, Roles, Age, KubeletVersion 513 expected: []metav1.TableRow{{Cells: []interface{}{"foo1", "Ready", "<none>", "<unknown>", ""}}}, 514 }, 515 { 516 node: api.Node{ 517 ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, 518 Spec: api.NodeSpec{Unschedulable: true}, 519 Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionTrue}}}, 520 }, 521 // Columns: Name, Status, Roles, Age, KubeletVersion 522 expected: []metav1.TableRow{{Cells: []interface{}{"foo2", "Ready,SchedulingDisabled", "<none>", "<unknown>", ""}}}, 523 }, 524 { 525 node: api.Node{ 526 ObjectMeta: metav1.ObjectMeta{Name: "foo3"}, 527 Status: api.NodeStatus{Conditions: []api.NodeCondition{ 528 {Type: api.NodeReady, Status: api.ConditionTrue}, 529 {Type: api.NodeReady, Status: api.ConditionTrue}}}, 530 }, 531 // Columns: Name, Status, Roles, Age, KubeletVersion 532 expected: []metav1.TableRow{{Cells: []interface{}{"foo3", "Ready", "<none>", "<unknown>", ""}}}, 533 }, 534 { 535 node: api.Node{ 536 ObjectMeta: metav1.ObjectMeta{Name: "foo4"}, 537 Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionFalse}}}, 538 }, 539 // Columns: Name, Status, Roles, Age, KubeletVersion 540 expected: []metav1.TableRow{{Cells: []interface{}{"foo4", "NotReady", "<none>", "<unknown>", ""}}}, 541 }, 542 { 543 node: api.Node{ 544 ObjectMeta: metav1.ObjectMeta{Name: "foo5"}, 545 Spec: api.NodeSpec{Unschedulable: true}, 546 Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionFalse}}}, 547 }, 548 // Columns: Name, Status, Roles, Age, KubeletVersion 549 expected: []metav1.TableRow{{Cells: []interface{}{"foo5", "NotReady,SchedulingDisabled", "<none>", "<unknown>", ""}}}, 550 }, 551 { 552 node: api.Node{ 553 ObjectMeta: metav1.ObjectMeta{Name: "foo6"}, 554 Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: "InvalidValue", Status: api.ConditionTrue}}}, 555 }, 556 // Columns: Name, Status, Roles, Age, KubeletVersion 557 expected: []metav1.TableRow{{Cells: []interface{}{"foo6", "Unknown", "<none>", "<unknown>", ""}}}, 558 }, 559 { 560 node: api.Node{ 561 ObjectMeta: metav1.ObjectMeta{Name: "foo7"}, 562 Status: api.NodeStatus{Conditions: []api.NodeCondition{{}}}, 563 }, 564 // Columns: Name, Status, Roles, Age, KubeletVersion 565 expected: []metav1.TableRow{{Cells: []interface{}{"foo7", "Unknown", "<none>", "<unknown>", ""}}}, 566 }, 567 { 568 node: api.Node{ 569 ObjectMeta: metav1.ObjectMeta{Name: "foo8"}, 570 Spec: api.NodeSpec{Unschedulable: true}, 571 Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: "InvalidValue", Status: api.ConditionTrue}}}, 572 }, 573 // Columns: Name, Status, Roles, Age, KubeletVersion 574 expected: []metav1.TableRow{{Cells: []interface{}{"foo8", "Unknown,SchedulingDisabled", "<none>", "<unknown>", ""}}}, 575 }, 576 { 577 node: api.Node{ 578 ObjectMeta: metav1.ObjectMeta{Name: "foo9"}, 579 Spec: api.NodeSpec{Unschedulable: true}, 580 Status: api.NodeStatus{Conditions: []api.NodeCondition{{}}}, 581 }, 582 // Columns: Name, Status, Roles, Age, KubeletVersion 583 expected: []metav1.TableRow{{Cells: []interface{}{"foo9", "Unknown,SchedulingDisabled", "<none>", "<unknown>", ""}}}, 584 }, 585 } 586 587 for i, test := range table { 588 rows, err := printNode(&test.node, printers.GenerateOptions{}) 589 if err != nil { 590 t.Fatalf("Error generating table rows for Node: %#v", err) 591 } 592 for i := range rows { 593 rows[i].Object.Object = nil 594 } 595 if !reflect.DeepEqual(test.expected, rows) { 596 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 597 } 598 } 599 } 600 601 func TestPrintNodeRole(t *testing.T) { 602 603 table := []struct { 604 node api.Node 605 expected []metav1.TableRow 606 }{ 607 { 608 node: api.Node{ 609 ObjectMeta: metav1.ObjectMeta{Name: "foo9"}, 610 }, 611 // Columns: Name, Status, Roles, Age, KubeletVersion 612 expected: []metav1.TableRow{{Cells: []interface{}{"foo9", "Unknown", "<none>", "<unknown>", ""}}}, 613 }, 614 { 615 node: api.Node{ 616 ObjectMeta: metav1.ObjectMeta{ 617 Name: "foo10", 618 Labels: map[string]string{"node-role.kubernetes.io/master": "", "node-role.kubernetes.io/control-plane": "", "node-role.kubernetes.io/proxy": "", "kubernetes.io/role": "node"}, 619 }, 620 }, 621 // Columns: Name, Status, Roles, Age, KubeletVersion 622 expected: []metav1.TableRow{{Cells: []interface{}{"foo10", "Unknown", "control-plane,master,node,proxy", "<unknown>", ""}}}, 623 }, 624 { 625 node: api.Node{ 626 ObjectMeta: metav1.ObjectMeta{ 627 Name: "foo11", 628 Labels: map[string]string{"kubernetes.io/role": "node"}, 629 }, 630 }, 631 // Columns: Name, Status, Roles, Age, KubeletVersion 632 expected: []metav1.TableRow{{Cells: []interface{}{"foo11", "Unknown", "node", "<unknown>", ""}}}, 633 }, 634 } 635 636 for i, test := range table { 637 rows, err := printNode(&test.node, printers.GenerateOptions{}) 638 if err != nil { 639 t.Fatalf("An error occurred generating table rows for Node: %#v", err) 640 } 641 for i := range rows { 642 rows[i].Object.Object = nil 643 } 644 if !reflect.DeepEqual(test.expected, rows) { 645 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 646 } 647 } 648 } 649 650 func TestPrintNodeOSImage(t *testing.T) { 651 652 table := []struct { 653 node api.Node 654 expected []metav1.TableRow 655 }{ 656 { 657 node: api.Node{ 658 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 659 Status: api.NodeStatus{ 660 NodeInfo: api.NodeSystemInfo{OSImage: "fake-os-image"}, 661 Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, 662 }, 663 }, 664 // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion 665 expected: []metav1.TableRow{ 666 { 667 Cells: []interface{}{"foo1", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "fake-os-image", "<unknown>", "<unknown>"}, 668 }, 669 }, 670 }, 671 { 672 node: api.Node{ 673 ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, 674 Status: api.NodeStatus{ 675 NodeInfo: api.NodeSystemInfo{KernelVersion: "fake-kernel-version"}, 676 Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, 677 }, 678 }, 679 // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion 680 expected: []metav1.TableRow{ 681 { 682 Cells: []interface{}{"foo2", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "<unknown>", "fake-kernel-version", "<unknown>"}, 683 }, 684 }, 685 }, 686 } 687 688 for i, test := range table { 689 rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) 690 if err != nil { 691 t.Fatalf("An error occurred generating table for Node: %#v", err) 692 } 693 for i := range rows { 694 rows[i].Object.Object = nil 695 } 696 if !reflect.DeepEqual(test.expected, rows) { 697 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 698 } 699 } 700 } 701 702 func TestPrintNodeKernelVersion(t *testing.T) { 703 704 table := []struct { 705 node api.Node 706 expected []metav1.TableRow 707 }{ 708 { 709 node: api.Node{ 710 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 711 Status: api.NodeStatus{ 712 NodeInfo: api.NodeSystemInfo{KernelVersion: "fake-kernel-version"}, 713 Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, 714 }, 715 }, 716 // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion 717 expected: []metav1.TableRow{ 718 { 719 Cells: []interface{}{"foo1", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "<unknown>", "fake-kernel-version", "<unknown>"}, 720 }, 721 }, 722 }, 723 { 724 node: api.Node{ 725 ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, 726 Status: api.NodeStatus{ 727 NodeInfo: api.NodeSystemInfo{OSImage: "fake-os-image"}, 728 Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, 729 }, 730 }, 731 // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion 732 expected: []metav1.TableRow{ 733 { 734 Cells: []interface{}{"foo2", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "fake-os-image", "<unknown>", "<unknown>"}, 735 }, 736 }, 737 }, 738 } 739 740 for i, test := range table { 741 rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) 742 if err != nil { 743 t.Fatalf("An error occurred generating table rows Node: %#v", err) 744 } 745 for i := range rows { 746 rows[i].Object.Object = nil 747 } 748 if !reflect.DeepEqual(test.expected, rows) { 749 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 750 } 751 } 752 } 753 754 func TestPrintNodeContainerRuntimeVersion(t *testing.T) { 755 756 table := []struct { 757 node api.Node 758 expected []metav1.TableRow 759 }{ 760 { 761 node: api.Node{ 762 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 763 Status: api.NodeStatus{ 764 NodeInfo: api.NodeSystemInfo{ContainerRuntimeVersion: "foo://1.2.3"}, 765 Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, 766 }, 767 }, 768 // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion 769 expected: []metav1.TableRow{ 770 { 771 Cells: []interface{}{"foo1", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "<unknown>", "<unknown>", "foo://1.2.3"}, 772 }, 773 }, 774 }, 775 { 776 node: api.Node{ 777 ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, 778 Status: api.NodeStatus{ 779 NodeInfo: api.NodeSystemInfo{}, 780 Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, 781 }, 782 }, 783 // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion 784 expected: []metav1.TableRow{ 785 { 786 Cells: []interface{}{"foo2", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "<unknown>", "<unknown>", "<unknown>"}, 787 }, 788 }, 789 }, 790 } 791 792 for i, test := range table { 793 rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) 794 if err != nil { 795 t.Fatalf("An error occurred generating table rows Node: %#v", err) 796 } 797 for i := range rows { 798 rows[i].Object.Object = nil 799 } 800 if !reflect.DeepEqual(test.expected, rows) { 801 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 802 } 803 } 804 } 805 806 func TestPrintNodeName(t *testing.T) { 807 808 table := []struct { 809 node api.Node 810 expected []metav1.TableRow 811 }{ 812 { 813 node: api.Node{ 814 ObjectMeta: metav1.ObjectMeta{Name: "127.0.0.1"}, 815 Status: api.NodeStatus{}, 816 }, 817 // Columns: Name, Status, Roles, Age, KubeletVersion 818 expected: []metav1.TableRow{{Cells: []interface{}{"127.0.0.1", "Unknown", "<none>", "<unknown>", ""}}}}, 819 { 820 node: api.Node{ 821 ObjectMeta: metav1.ObjectMeta{Name: ""}, 822 Status: api.NodeStatus{}, 823 }, 824 // Columns: Name, Status, Roles, Age, KubeletVersion 825 expected: []metav1.TableRow{{Cells: []interface{}{"", "Unknown", "<none>", "<unknown>", ""}}}, 826 }, 827 } 828 829 for i, test := range table { 830 rows, err := printNode(&test.node, printers.GenerateOptions{}) 831 if err != nil { 832 t.Fatalf("An error occurred generating table rows Node: %#v", err) 833 } 834 for i := range rows { 835 rows[i].Object.Object = nil 836 } 837 if !reflect.DeepEqual(test.expected, rows) { 838 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 839 } 840 } 841 } 842 843 func TestPrintNodeExternalIP(t *testing.T) { 844 845 table := []struct { 846 node api.Node 847 expected []metav1.TableRow 848 }{ 849 { 850 node: api.Node{ 851 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 852 Status: api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}}, 853 }, 854 // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion 855 expected: []metav1.TableRow{ 856 { 857 Cells: []interface{}{"foo1", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "<unknown>", "<unknown>", "<unknown>"}, 858 }, 859 }, 860 }, 861 { 862 node: api.Node{ 863 ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, 864 Status: api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeInternalIP, Address: "1.1.1.1"}}}, 865 }, 866 // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion 867 expected: []metav1.TableRow{ 868 { 869 Cells: []interface{}{"foo2", "Unknown", "<none>", "<unknown>", "", "1.1.1.1", "<none>", "<unknown>", "<unknown>", "<unknown>"}, 870 }, 871 }, 872 }, 873 { 874 node: api.Node{ 875 ObjectMeta: metav1.ObjectMeta{Name: "foo3"}, 876 Status: api.NodeStatus{Addresses: []api.NodeAddress{ 877 {Type: api.NodeExternalIP, Address: "2.2.2.2"}, 878 {Type: api.NodeInternalIP, Address: "3.3.3.3"}, 879 {Type: api.NodeExternalIP, Address: "4.4.4.4"}, 880 }}, 881 }, 882 expected: []metav1.TableRow{ 883 { 884 Cells: []interface{}{"foo3", "Unknown", "<none>", "<unknown>", "", "3.3.3.3", "2.2.2.2", "<unknown>", "<unknown>", "<unknown>"}, 885 }, 886 }, 887 }, 888 } 889 890 for i, test := range table { 891 rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) 892 if err != nil { 893 t.Fatalf("An error occurred generating table rows Node: %#v", err) 894 } 895 for i := range rows { 896 rows[i].Object.Object = nil 897 } 898 if !reflect.DeepEqual(test.expected, rows) { 899 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 900 } 901 } 902 } 903 904 func TestPrintNodeInternalIP(t *testing.T) { 905 906 table := []struct { 907 node api.Node 908 expected []metav1.TableRow 909 }{ 910 { 911 node: api.Node{ 912 ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, 913 Status: api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeInternalIP, Address: "1.1.1.1"}}}, 914 }, 915 // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion 916 expected: []metav1.TableRow{ 917 { 918 Cells: []interface{}{"foo1", "Unknown", "<none>", "<unknown>", "", "1.1.1.1", "<none>", "<unknown>", "<unknown>", "<unknown>"}, 919 }, 920 }, 921 }, 922 { 923 node: api.Node{ 924 ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, 925 Status: api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}}, 926 }, 927 // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion 928 expected: []metav1.TableRow{ 929 { 930 Cells: []interface{}{"foo2", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "<unknown>", "<unknown>", "<unknown>"}, 931 }, 932 }, 933 }, 934 { 935 node: api.Node{ 936 ObjectMeta: metav1.ObjectMeta{Name: "foo3"}, 937 Status: api.NodeStatus{Addresses: []api.NodeAddress{ 938 {Type: api.NodeInternalIP, Address: "2.2.2.2"}, 939 {Type: api.NodeExternalIP, Address: "3.3.3.3"}, 940 {Type: api.NodeInternalIP, Address: "4.4.4.4"}, 941 }}, 942 }, 943 // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion 944 expected: []metav1.TableRow{ 945 { 946 Cells: []interface{}{"foo3", "Unknown", "<none>", "<unknown>", "", "2.2.2.2", "3.3.3.3", "<unknown>", "<unknown>", "<unknown>"}, 947 }, 948 }, 949 }, 950 } 951 952 for i, test := range table { 953 rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) 954 if err != nil { 955 t.Fatalf("An error occurred generating table rows Node: %#v", err) 956 } 957 for i := range rows { 958 rows[i].Object.Object = nil 959 } 960 if !reflect.DeepEqual(test.expected, rows) { 961 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 962 } 963 } 964 } 965 966 func TestPrintIngress(t *testing.T) { 967 ingress := networking.Ingress{ 968 ObjectMeta: metav1.ObjectMeta{ 969 Name: "test1", 970 CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, 971 }, 972 Spec: networking.IngressSpec{ 973 IngressClassName: utilpointer.StringPtr("foo"), 974 DefaultBackend: &networking.IngressBackend{ 975 Service: &networking.IngressServiceBackend{ 976 Name: "default-backend", 977 Port: networking.ServiceBackendPort{ 978 Name: "default-backend", 979 Number: 80, 980 }, 981 }, 982 }, 983 }, 984 Status: networking.IngressStatus{ 985 LoadBalancer: networking.IngressLoadBalancerStatus{ 986 Ingress: []networking.IngressLoadBalancerIngress{ 987 { 988 IP: "2.3.4.5", 989 Hostname: "localhost.localdomain", 990 }, 991 }, 992 }, 993 }, 994 } 995 // Columns: Name, Hosts, Address, Ports, Age 996 expected := []metav1.TableRow{{Cells: []interface{}{"test1", "foo", "*", "2.3.4.5", "80", "10y"}}} 997 998 rows, err := printIngress(&ingress, printers.GenerateOptions{}) 999 if err != nil { 1000 t.Fatalf("Error generating table rows for Ingress: %#v", err) 1001 } 1002 rows[0].Object.Object = nil 1003 if !reflect.DeepEqual(expected, rows) { 1004 t.Errorf("mismatch: %s", cmp.Diff(expected, rows)) 1005 } 1006 } 1007 1008 func TestPrintIngressClass(t *testing.T) { 1009 testCases := []struct { 1010 name string 1011 ingressClass *networking.IngressClass 1012 expected []metav1.TableRow 1013 }{{ 1014 name: "example with params", 1015 ingressClass: &networking.IngressClass{ 1016 ObjectMeta: metav1.ObjectMeta{ 1017 Name: "test1", 1018 CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, 1019 }, 1020 Spec: networking.IngressClassSpec{ 1021 Controller: "example.com/controller", 1022 Parameters: &networking.IngressClassParametersReference{Kind: "customgroup", Name: "example"}, 1023 }, 1024 }, 1025 expected: []metav1.TableRow{{Cells: []interface{}{"test1", "example.com/controller", "customgroup/example", "10y"}}}, 1026 }, { 1027 name: "example with params + API Group", 1028 ingressClass: &networking.IngressClass{ 1029 ObjectMeta: metav1.ObjectMeta{ 1030 Name: "test1", 1031 CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, 1032 }, 1033 Spec: networking.IngressClassSpec{ 1034 Controller: "example.com/controller", 1035 Parameters: &networking.IngressClassParametersReference{ 1036 APIGroup: utilpointer.StringPtr("example.com"), 1037 Kind: "customgroup", 1038 Name: "example", 1039 }, 1040 }, 1041 }, 1042 expected: []metav1.TableRow{{Cells: []interface{}{"test1", "example.com/controller", "customgroup.example.com/example", "10y"}}}, 1043 }, { 1044 name: "example without params", 1045 ingressClass: &networking.IngressClass{ 1046 ObjectMeta: metav1.ObjectMeta{ 1047 Name: "test2", 1048 CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-11, 0, 0)}, 1049 }, 1050 Spec: networking.IngressClassSpec{ 1051 Controller: "example.com/controller2", 1052 }, 1053 }, 1054 expected: []metav1.TableRow{{Cells: []interface{}{"test2", "example.com/controller2", "<none>", "11y"}}}, 1055 }} 1056 1057 for _, testCase := range testCases { 1058 t.Run(testCase.name, func(t *testing.T) { 1059 rows, err := printIngressClass(testCase.ingressClass, printers.GenerateOptions{}) 1060 if err != nil { 1061 t.Fatalf("Error generating table rows for Ingress: %#v", err) 1062 } 1063 for i := range rows { 1064 rows[i].Object.Object = nil 1065 } 1066 if !reflect.DeepEqual(testCase.expected, rows) { 1067 t.Errorf("mismatch: %s", cmp.Diff(testCase.expected, rows)) 1068 } 1069 }) 1070 } 1071 } 1072 1073 func TestPrintServiceLoadBalancer(t *testing.T) { 1074 tests := []struct { 1075 service api.Service 1076 options printers.GenerateOptions 1077 expected []metav1.TableRow 1078 }{ 1079 // Test load balancer service with multiple external IP's 1080 { 1081 service: api.Service{ 1082 ObjectMeta: metav1.ObjectMeta{Name: "service1"}, 1083 Spec: api.ServiceSpec{ 1084 ClusterIPs: []string{"1.2.3.4"}, 1085 Type: "LoadBalancer", 1086 Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}}, 1087 }, 1088 Status: api.ServiceStatus{ 1089 LoadBalancer: api.LoadBalancerStatus{ 1090 Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}}}, 1091 }, 1092 }, 1093 options: printers.GenerateOptions{}, 1094 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 1095 expected: []metav1.TableRow{{Cells: []interface{}{"service1", "LoadBalancer", "1.2.3.4", "2.3.4.5,3.4.5.6", "80/TCP", "<unknown>"}}}, 1096 }, 1097 // Test load balancer service with pending external IP. 1098 { 1099 service: api.Service{ 1100 ObjectMeta: metav1.ObjectMeta{Name: "service2"}, 1101 Spec: api.ServiceSpec{ 1102 ClusterIPs: []string{"1.3.4.5"}, 1103 Type: "LoadBalancer", 1104 Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}, {Port: 7777, Protocol: "SCTP"}}, 1105 }, 1106 }, 1107 options: printers.GenerateOptions{}, 1108 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 1109 expected: []metav1.TableRow{{Cells: []interface{}{"service2", "LoadBalancer", "1.3.4.5", "<pending>", "80/TCP,8090/UDP,8000/TCP,7777/SCTP", "<unknown>"}}}, 1110 }, 1111 // Test load balancer service with multiple ports. 1112 { 1113 service: api.Service{ 1114 ObjectMeta: metav1.ObjectMeta{Name: "service3"}, 1115 Spec: api.ServiceSpec{ 1116 ClusterIPs: []string{"1.4.5.6"}, 1117 Type: "LoadBalancer", 1118 Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}}, 1119 }, 1120 Status: api.ServiceStatus{ 1121 LoadBalancer: api.LoadBalancerStatus{ 1122 Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}}}, 1123 }, 1124 }, 1125 options: printers.GenerateOptions{}, 1126 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 1127 expected: []metav1.TableRow{{Cells: []interface{}{"service3", "LoadBalancer", "1.4.5.6", "2.3.4.5", "80/TCP,8090/UDP,8000/TCP", "<unknown>"}}}, 1128 }, 1129 // Long external IP's list gets elided. 1130 { 1131 service: api.Service{ 1132 ObjectMeta: metav1.ObjectMeta{Name: "service4"}, 1133 Spec: api.ServiceSpec{ 1134 ClusterIPs: []string{"1.5.6.7"}, 1135 Type: "LoadBalancer", 1136 Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}}, 1137 }, 1138 Status: api.ServiceStatus{ 1139 LoadBalancer: api.LoadBalancerStatus{ 1140 Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}, {IP: "5.6.7.8", Hostname: "host5678"}}}, 1141 }, 1142 }, 1143 options: printers.GenerateOptions{}, 1144 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 1145 expected: []metav1.TableRow{{Cells: []interface{}{"service4", "LoadBalancer", "1.5.6.7", "2.3.4.5,3.4.5...", "80/TCP,8090/UDP,8000/TCP", "<unknown>"}}}, 1146 }, 1147 // Generate options: Wide, includes selectors. 1148 { 1149 service: api.Service{ 1150 ObjectMeta: metav1.ObjectMeta{Name: "service4"}, 1151 Spec: api.ServiceSpec{ 1152 ClusterIPs: []string{"1.5.6.7"}, 1153 Type: "LoadBalancer", 1154 Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}}, 1155 Selector: map[string]string{"foo": "bar"}, 1156 }, 1157 Status: api.ServiceStatus{ 1158 LoadBalancer: api.LoadBalancerStatus{ 1159 Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}, {IP: "5.6.7.8", Hostname: "host5678"}}}, 1160 }, 1161 }, 1162 options: printers.GenerateOptions{Wide: true}, 1163 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 1164 expected: []metav1.TableRow{{Cells: []interface{}{"service4", "LoadBalancer", "1.5.6.7", "2.3.4.5,3.4.5.6,5.6.7.8", "80/TCP,8090/UDP,8000/TCP", "<unknown>", "foo=bar"}}}, 1165 }, 1166 } 1167 1168 for i, test := range tests { 1169 rows, err := printService(&test.service, test.options) 1170 if err != nil { 1171 t.Fatalf("Error printing table rows for Service: %#v", err) 1172 } 1173 for i := range rows { 1174 rows[i].Object.Object = nil 1175 } 1176 if !reflect.DeepEqual(test.expected, rows) { 1177 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 1178 } 1179 } 1180 } 1181 1182 func TestPrintPod(t *testing.T) { 1183 tests := []struct { 1184 pod api.Pod 1185 expect []metav1.TableRow 1186 }{ 1187 { 1188 // Test name, num of containers, restarts, container ready status 1189 api.Pod{ 1190 ObjectMeta: metav1.ObjectMeta{Name: "test1"}, 1191 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1192 Status: api.PodStatus{ 1193 Phase: "podPhase", 1194 ContainerStatuses: []api.ContainerStatus{ 1195 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1196 {RestartCount: 3}, 1197 }, 1198 }, 1199 }, 1200 []metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", "6", "<unknown>"}}}, 1201 }, 1202 { 1203 // Test container error overwrites pod phase 1204 api.Pod{ 1205 ObjectMeta: metav1.ObjectMeta{Name: "test2"}, 1206 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1207 Status: api.PodStatus{ 1208 Phase: "podPhase", 1209 ContainerStatuses: []api.ContainerStatus{ 1210 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1211 {State: api.ContainerState{Waiting: &api.ContainerStateWaiting{Reason: "ContainerWaitingReason"}}, RestartCount: 3}, 1212 }, 1213 }, 1214 }, 1215 []metav1.TableRow{{Cells: []interface{}{"test2", "1/2", "ContainerWaitingReason", "6", "<unknown>"}}}, 1216 }, 1217 { 1218 // Test the same as the above but with Terminated state and the first container overwrites the rest 1219 api.Pod{ 1220 ObjectMeta: metav1.ObjectMeta{Name: "test3"}, 1221 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1222 Status: api.PodStatus{ 1223 Phase: "podPhase", 1224 ContainerStatuses: []api.ContainerStatus{ 1225 {State: api.ContainerState{Waiting: &api.ContainerStateWaiting{Reason: "ContainerWaitingReason"}}, RestartCount: 3}, 1226 {State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "ContainerTerminatedReason"}}, RestartCount: 3}, 1227 }, 1228 }, 1229 }, 1230 []metav1.TableRow{{Cells: []interface{}{"test3", "0/2", "ContainerWaitingReason", "6", "<unknown>"}}}, 1231 }, 1232 { 1233 // Test ready is not enough for reporting running 1234 api.Pod{ 1235 ObjectMeta: metav1.ObjectMeta{Name: "test4"}, 1236 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1237 Status: api.PodStatus{ 1238 Phase: "podPhase", 1239 ContainerStatuses: []api.ContainerStatus{ 1240 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1241 {Ready: true, RestartCount: 3}, 1242 }, 1243 }, 1244 }, 1245 []metav1.TableRow{{Cells: []interface{}{"test4", "1/2", "podPhase", "6", "<unknown>"}}}, 1246 }, 1247 { 1248 // Test ready is not enough for reporting running 1249 api.Pod{ 1250 ObjectMeta: metav1.ObjectMeta{Name: "test5"}, 1251 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1252 Status: api.PodStatus{ 1253 Reason: "podReason", 1254 Phase: "podPhase", 1255 ContainerStatuses: []api.ContainerStatus{ 1256 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1257 {Ready: true, RestartCount: 3}, 1258 }, 1259 }, 1260 }, 1261 []metav1.TableRow{{Cells: []interface{}{"test5", "1/2", "podReason", "6", "<unknown>"}}}, 1262 }, 1263 { 1264 // Test pod has 2 containers, one is running and the other is completed, w/o ready condition 1265 api.Pod{ 1266 ObjectMeta: metav1.ObjectMeta{Name: "test6"}, 1267 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1268 Status: api.PodStatus{ 1269 Phase: "Running", 1270 Reason: "", 1271 ContainerStatuses: []api.ContainerStatus{ 1272 {Ready: true, RestartCount: 3, State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Completed", ExitCode: 0}}}, 1273 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1274 }, 1275 }, 1276 }, 1277 []metav1.TableRow{{Cells: []interface{}{"test6", "1/2", "NotReady", "6", "<unknown>"}}}, 1278 }, 1279 { 1280 // Test pod has 2 containers, one is running and the other is completed, with ready condition 1281 api.Pod{ 1282 ObjectMeta: metav1.ObjectMeta{Name: "test6"}, 1283 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1284 Status: api.PodStatus{ 1285 Phase: "Running", 1286 Reason: "", 1287 ContainerStatuses: []api.ContainerStatus{ 1288 {Ready: true, RestartCount: 3, State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Completed", ExitCode: 0}}}, 1289 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1290 }, 1291 Conditions: []api.PodCondition{ 1292 {Type: api.PodReady, Status: api.ConditionTrue, LastProbeTime: metav1.Time{Time: time.Now()}}, 1293 }, 1294 }, 1295 }, 1296 []metav1.TableRow{{Cells: []interface{}{"test6", "1/2", "Running", "6", "<unknown>"}}}, 1297 }, 1298 { 1299 // Test pod has 1 init container restarting and 1 container not running 1300 api.Pod{ 1301 ObjectMeta: metav1.ObjectMeta{Name: "test7"}, 1302 Spec: api.PodSpec{InitContainers: make([]api.Container, 1), Containers: make([]api.Container, 1)}, 1303 Status: api.PodStatus{ 1304 Phase: "podPhase", 1305 InitContainerStatuses: []api.ContainerStatus{ 1306 { 1307 Ready: false, 1308 RestartCount: 3, 1309 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1310 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, 1311 }, 1312 }, 1313 ContainerStatuses: []api.ContainerStatus{ 1314 { 1315 Ready: false, 1316 RestartCount: 0, 1317 State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, 1318 }, 1319 }, 1320 }, 1321 }, 1322 []metav1.TableRow{{Cells: []interface{}{"test7", "0/1", "Init:0/1", "3 (10s ago)", "<unknown>"}}}, 1323 }, 1324 { 1325 // Test pod has 2 init containers, one restarting and the other not running, and 1 container not running 1326 api.Pod{ 1327 ObjectMeta: metav1.ObjectMeta{Name: "test8"}, 1328 Spec: api.PodSpec{InitContainers: make([]api.Container, 2), Containers: make([]api.Container, 1)}, 1329 Status: api.PodStatus{ 1330 Phase: "podPhase", 1331 InitContainerStatuses: []api.ContainerStatus{ 1332 { 1333 Ready: false, 1334 RestartCount: 3, 1335 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1336 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, 1337 }, 1338 { 1339 Ready: false, 1340 State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, 1341 }, 1342 }, 1343 ContainerStatuses: []api.ContainerStatus{ 1344 { 1345 Ready: false, 1346 State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, 1347 }, 1348 }, 1349 }, 1350 }, 1351 []metav1.TableRow{{Cells: []interface{}{"test8", "0/1", "Init:0/2", "3 (10s ago)", "<unknown>"}}}, 1352 }, 1353 { 1354 // Test pod has 2 init containers, one completed without restarts and the other restarting, and 1 container not running 1355 api.Pod{ 1356 ObjectMeta: metav1.ObjectMeta{Name: "test9"}, 1357 Spec: api.PodSpec{InitContainers: make([]api.Container, 2), Containers: make([]api.Container, 1)}, 1358 Status: api.PodStatus{ 1359 Phase: "podPhase", 1360 InitContainerStatuses: []api.ContainerStatus{ 1361 { 1362 Ready: false, 1363 State: api.ContainerState{Terminated: &api.ContainerStateTerminated{}}, 1364 }, 1365 { 1366 Ready: false, 1367 RestartCount: 3, 1368 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1369 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, 1370 }, 1371 }, 1372 ContainerStatuses: []api.ContainerStatus{ 1373 { 1374 Ready: false, 1375 State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, 1376 }, 1377 }, 1378 }, 1379 }, 1380 []metav1.TableRow{{Cells: []interface{}{"test9", "0/1", "Init:1/2", "3 (10s ago)", "<unknown>"}}}, 1381 }, 1382 { 1383 // Test pod has 2 init containers, one completed with restarts and the other restarting, and 1 container not running 1384 api.Pod{ 1385 ObjectMeta: metav1.ObjectMeta{Name: "test10"}, 1386 Spec: api.PodSpec{InitContainers: make([]api.Container, 2), Containers: make([]api.Container, 1)}, 1387 Status: api.PodStatus{ 1388 Phase: "podPhase", 1389 InitContainerStatuses: []api.ContainerStatus{ 1390 { 1391 Ready: false, 1392 RestartCount: 2, 1393 State: api.ContainerState{Terminated: &api.ContainerStateTerminated{}}, 1394 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-2 * time.Minute))}}, 1395 }, 1396 { 1397 Ready: false, 1398 RestartCount: 3, 1399 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1400 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, 1401 }, 1402 }, 1403 ContainerStatuses: []api.ContainerStatus{ 1404 { 1405 Ready: false, 1406 State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, 1407 }, 1408 }, 1409 }, 1410 }, 1411 []metav1.TableRow{{Cells: []interface{}{"test10", "0/1", "Init:1/2", "5 (10s ago)", "<unknown>"}}}, 1412 }, 1413 { 1414 // Test pod has 1 init container completed with restarts and one container restarting 1415 api.Pod{ 1416 ObjectMeta: metav1.ObjectMeta{Name: "test11"}, 1417 Spec: api.PodSpec{InitContainers: make([]api.Container, 1), Containers: make([]api.Container, 1)}, 1418 Status: api.PodStatus{ 1419 Phase: "Running", 1420 InitContainerStatuses: []api.ContainerStatus{ 1421 { 1422 Ready: false, 1423 RestartCount: 2, 1424 State: api.ContainerState{Terminated: &api.ContainerStateTerminated{}}, 1425 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-2 * time.Minute))}}, 1426 }, 1427 }, 1428 ContainerStatuses: []api.ContainerStatus{ 1429 { 1430 Ready: false, 1431 RestartCount: 4, 1432 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1433 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * time.Second))}}, 1434 }, 1435 }, 1436 }, 1437 }, 1438 []metav1.TableRow{{Cells: []interface{}{"test11", "0/1", "Running", "4 (20s ago)", "<unknown>"}}}, 1439 }, 1440 { 1441 // Test pod has 1 container that restarted 5d ago 1442 api.Pod{ 1443 ObjectMeta: metav1.ObjectMeta{Name: "test12"}, 1444 Spec: api.PodSpec{Containers: make([]api.Container, 1)}, 1445 Status: api.PodStatus{ 1446 Phase: "Running", 1447 ContainerStatuses: []api.ContainerStatus{ 1448 { 1449 Ready: true, 1450 RestartCount: 3, 1451 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1452 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-5 * 24 * time.Hour))}}, 1453 }, 1454 }, 1455 }, 1456 }, 1457 []metav1.TableRow{{Cells: []interface{}{"test12", "1/1", "Running", "3 (5d ago)", "<unknown>"}}}, 1458 }, 1459 { 1460 // Test pod has 2 containers, one has never restarted and the other has restarted 10d ago 1461 api.Pod{ 1462 ObjectMeta: metav1.ObjectMeta{Name: "test13"}, 1463 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1464 Status: api.PodStatus{ 1465 Phase: "Running", 1466 ContainerStatuses: []api.ContainerStatus{ 1467 { 1468 Ready: true, 1469 RestartCount: 0, 1470 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1471 }, 1472 { 1473 Ready: true, 1474 RestartCount: 3, 1475 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1476 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * 24 * time.Hour))}}, 1477 }, 1478 }, 1479 }, 1480 }, 1481 []metav1.TableRow{{Cells: []interface{}{"test13", "2/2", "Running", "3 (10d ago)", "<unknown>"}}}, 1482 }, 1483 { 1484 // Test pod has 2 containers, one restarted 5d ago and the other restarted 20d ago 1485 api.Pod{ 1486 ObjectMeta: metav1.ObjectMeta{Name: "test14"}, 1487 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1488 Status: api.PodStatus{ 1489 Phase: "Running", 1490 ContainerStatuses: []api.ContainerStatus{ 1491 { 1492 Ready: true, 1493 RestartCount: 6, 1494 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1495 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-5 * 24 * time.Hour))}}, 1496 }, 1497 { 1498 Ready: true, 1499 RestartCount: 3, 1500 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1501 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * 24 * time.Hour))}}, 1502 }, 1503 }, 1504 }, 1505 }, 1506 []metav1.TableRow{{Cells: []interface{}{"test14", "2/2", "Running", "9 (5d ago)", "<unknown>"}}}, 1507 }, 1508 { 1509 // Test PodScheduled condition with reason WaitingForGates 1510 api.Pod{ 1511 ObjectMeta: metav1.ObjectMeta{Name: "test15"}, 1512 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1513 Status: api.PodStatus{ 1514 Phase: "podPhase", 1515 Conditions: []api.PodCondition{ 1516 { 1517 Type: api.PodScheduled, 1518 Status: api.ConditionFalse, 1519 Reason: apiv1.PodReasonSchedulingGated, 1520 }, 1521 }, 1522 }, 1523 }, 1524 []metav1.TableRow{{Cells: []interface{}{"test15", "0/2", apiv1.PodReasonSchedulingGated, "0", "<unknown>"}}}, 1525 }, 1526 } 1527 1528 for i, test := range tests { 1529 rows, err := printPod(&test.pod, printers.GenerateOptions{}) 1530 if err != nil { 1531 t.Fatal(err) 1532 } 1533 for i := range rows { 1534 rows[i].Object.Object = nil 1535 } 1536 if !reflect.DeepEqual(test.expect, rows) { 1537 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows)) 1538 } 1539 } 1540 } 1541 1542 func TestPrintPodWithRestartableInitContainer(t *testing.T) { 1543 tests := []struct { 1544 pod api.Pod 1545 expect []metav1.TableRow 1546 }{ 1547 { 1548 // Test pod has 2 restartable init containers, the first one running but not started. 1549 api.Pod{ 1550 ObjectMeta: metav1.ObjectMeta{Name: "test1"}, 1551 Spec: api.PodSpec{ 1552 InitContainers: []api.Container{ 1553 {Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways}, 1554 {Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways}, 1555 }, Containers: make([]api.Container, 1)}, 1556 Status: api.PodStatus{ 1557 Phase: "Pending", 1558 InitContainerStatuses: []api.ContainerStatus{ 1559 { 1560 Name: "restartable-init-1", 1561 Ready: false, 1562 RestartCount: 3, 1563 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1564 Started: utilpointer.Bool(false), 1565 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, 1566 }, 1567 { 1568 Name: "restartable-init-2", 1569 Ready: false, 1570 State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, 1571 Started: utilpointer.Bool(false), 1572 }, 1573 }, 1574 ContainerStatuses: []api.ContainerStatus{ 1575 { 1576 Ready: false, 1577 RestartCount: 0, 1578 State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, 1579 }, 1580 }, 1581 Conditions: []api.PodCondition{ 1582 {Type: api.PodInitialized, Status: api.ConditionFalse}, 1583 }, 1584 }, 1585 }, 1586 []metav1.TableRow{{Cells: []interface{}{"test1", "0/3", "Init:0/2", "3 (10s ago)", "<unknown>"}}}, 1587 }, 1588 { 1589 // Test pod has 2 restartable init containers, the first one started and the second one running but not started. 1590 api.Pod{ 1591 ObjectMeta: metav1.ObjectMeta{Name: "test1"}, 1592 Spec: api.PodSpec{ 1593 InitContainers: []api.Container{ 1594 {Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways}, 1595 {Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways}, 1596 }, Containers: make([]api.Container, 1)}, 1597 Status: api.PodStatus{ 1598 Phase: "Pending", 1599 InitContainerStatuses: []api.ContainerStatus{ 1600 { 1601 Name: "restartable-init-1", 1602 Ready: false, 1603 RestartCount: 3, 1604 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1605 Started: utilpointer.Bool(true), 1606 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, 1607 }, 1608 { 1609 Name: "restartable-init-2", 1610 Ready: false, 1611 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1612 Started: utilpointer.Bool(false), 1613 }, 1614 }, 1615 ContainerStatuses: []api.ContainerStatus{ 1616 { 1617 Ready: false, 1618 RestartCount: 0, 1619 State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, 1620 }, 1621 }, 1622 Conditions: []api.PodCondition{ 1623 {Type: api.PodInitialized, Status: api.ConditionFalse}, 1624 }, 1625 }, 1626 }, 1627 []metav1.TableRow{{Cells: []interface{}{"test1", "0/3", "Init:1/2", "3 (10s ago)", "<unknown>"}}}, 1628 }, 1629 { 1630 // Test pod has 2 restartable init containers started and 1 container running 1631 api.Pod{ 1632 ObjectMeta: metav1.ObjectMeta{Name: "test2"}, 1633 Spec: api.PodSpec{ 1634 InitContainers: []api.Container{ 1635 {Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways}, 1636 {Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways}, 1637 }, Containers: make([]api.Container, 1)}, 1638 Status: api.PodStatus{ 1639 Phase: "Running", 1640 InitContainerStatuses: []api.ContainerStatus{ 1641 { 1642 Name: "restartable-init-1", 1643 Ready: false, 1644 RestartCount: 3, 1645 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1646 Started: utilpointer.Bool(true), 1647 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, 1648 }, 1649 { 1650 Name: "restartable-init-2", 1651 Ready: false, 1652 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1653 Started: utilpointer.Bool(true), 1654 }, 1655 }, 1656 ContainerStatuses: []api.ContainerStatus{ 1657 { 1658 Ready: true, 1659 RestartCount: 4, 1660 State: api.ContainerState{Running: &api.ContainerStateRunning{}}, 1661 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * time.Second))}}, 1662 }, 1663 }, 1664 Conditions: []api.PodCondition{ 1665 {Type: api.PodInitialized, Status: api.ConditionTrue}, 1666 }, 1667 }, 1668 }, 1669 []metav1.TableRow{{Cells: []interface{}{"test2", "1/3", "Running", "7 (10s ago)", "<unknown>"}}}, 1670 }, 1671 { 1672 // Test pod has 2 restartable init containers completed with non-zero and 1 container completed 1673 api.Pod{ 1674 ObjectMeta: metav1.ObjectMeta{Name: "test3"}, 1675 Spec: api.PodSpec{ 1676 InitContainers: []api.Container{ 1677 {Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways}, 1678 {Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways}, 1679 }, Containers: make([]api.Container, 1)}, 1680 Status: api.PodStatus{ 1681 Phase: "Succeeded", 1682 InitContainerStatuses: []api.ContainerStatus{ 1683 { 1684 Name: "restartable-init-1", 1685 Ready: false, 1686 RestartCount: 3, 1687 State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Error", ExitCode: 137}}, 1688 Started: utilpointer.Bool(false), 1689 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, 1690 }, 1691 { 1692 Name: "restartable-init-2", 1693 Ready: false, 1694 State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Error", ExitCode: 137}}, 1695 Started: utilpointer.Bool(false), 1696 }, 1697 }, 1698 ContainerStatuses: []api.ContainerStatus{ 1699 { 1700 Ready: false, 1701 RestartCount: 4, 1702 State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Completed", ExitCode: 0}}, 1703 LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * time.Second))}}, 1704 }, 1705 }, 1706 Conditions: []api.PodCondition{ 1707 {Type: api.PodInitialized, Status: api.ConditionTrue}, 1708 }, 1709 }, 1710 }, 1711 []metav1.TableRow{ 1712 { 1713 Cells: []interface{}{"test3", "0/3", "Completed", "7 (10s ago)", "<unknown>"}, 1714 Conditions: []metav1.TableRowCondition{ 1715 {Type: metav1.RowCompleted, Status: metav1.ConditionTrue, Reason: "Succeeded", Message: "The pod has completed successfully."}, 1716 }, 1717 }, 1718 }, 1719 }, 1720 } 1721 1722 for i, test := range tests { 1723 rows, err := printPod(&test.pod, printers.GenerateOptions{}) 1724 if err != nil { 1725 t.Fatal(err) 1726 } 1727 for i := range rows { 1728 rows[i].Object.Object = nil 1729 } 1730 if !reflect.DeepEqual(test.expect, rows) { 1731 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows)) 1732 } 1733 } 1734 } 1735 1736 func TestPrintPodwide(t *testing.T) { 1737 condition1 := "condition1" 1738 condition2 := "condition2" 1739 condition3 := "condition3" 1740 tests := []struct { 1741 pod api.Pod 1742 expect []metav1.TableRow 1743 }{ 1744 { 1745 // Test when the NodeName and PodIP are not none 1746 api.Pod{ 1747 ObjectMeta: metav1.ObjectMeta{Name: "test1"}, 1748 Spec: api.PodSpec{ 1749 Containers: make([]api.Container, 2), 1750 NodeName: "test1", 1751 ReadinessGates: []api.PodReadinessGate{ 1752 { 1753 ConditionType: api.PodConditionType(condition1), 1754 }, 1755 { 1756 ConditionType: api.PodConditionType(condition2), 1757 }, 1758 { 1759 ConditionType: api.PodConditionType(condition3), 1760 }, 1761 }, 1762 }, 1763 Status: api.PodStatus{ 1764 Conditions: []api.PodCondition{ 1765 { 1766 Type: api.PodConditionType(condition1), 1767 Status: api.ConditionFalse, 1768 }, 1769 { 1770 Type: api.PodConditionType(condition2), 1771 Status: api.ConditionTrue, 1772 }, 1773 }, 1774 Phase: "podPhase", 1775 PodIPs: []api.PodIP{{IP: "1.1.1.1"}}, 1776 ContainerStatuses: []api.ContainerStatus{ 1777 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1778 {RestartCount: 3}, 1779 }, 1780 NominatedNodeName: "node1", 1781 }, 1782 }, 1783 []metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", "6", "<unknown>", "1.1.1.1", "test1", "node1", "1/3"}}}, 1784 }, 1785 { 1786 // Test when the NodeName and PodIP are not none 1787 api.Pod{ 1788 ObjectMeta: metav1.ObjectMeta{Name: "test1"}, 1789 Spec: api.PodSpec{ 1790 Containers: make([]api.Container, 2), 1791 NodeName: "test1", 1792 ReadinessGates: []api.PodReadinessGate{ 1793 { 1794 ConditionType: api.PodConditionType(condition1), 1795 }, 1796 { 1797 ConditionType: api.PodConditionType(condition2), 1798 }, 1799 { 1800 ConditionType: api.PodConditionType(condition3), 1801 }, 1802 }, 1803 }, 1804 Status: api.PodStatus{ 1805 Conditions: []api.PodCondition{ 1806 { 1807 Type: api.PodConditionType(condition1), 1808 Status: api.ConditionFalse, 1809 }, 1810 { 1811 Type: api.PodConditionType(condition2), 1812 Status: api.ConditionTrue, 1813 }, 1814 }, 1815 Phase: "podPhase", 1816 PodIPs: []api.PodIP{{IP: "1.1.1.1"}, {IP: "2001:db8::"}}, 1817 ContainerStatuses: []api.ContainerStatus{ 1818 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1819 {RestartCount: 3}, 1820 }, 1821 NominatedNodeName: "node1", 1822 }, 1823 }, 1824 []metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", "6", "<unknown>", "1.1.1.1", "test1", "node1", "1/3"}}}, 1825 }, 1826 { 1827 // Test when the NodeName and PodIP are none 1828 api.Pod{ 1829 ObjectMeta: metav1.ObjectMeta{Name: "test2"}, 1830 Spec: api.PodSpec{ 1831 Containers: make([]api.Container, 2), 1832 NodeName: "", 1833 }, 1834 Status: api.PodStatus{ 1835 Phase: "podPhase", 1836 ContainerStatuses: []api.ContainerStatus{ 1837 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1838 {State: api.ContainerState{Waiting: &api.ContainerStateWaiting{Reason: "ContainerWaitingReason"}}, RestartCount: 3}, 1839 }, 1840 }, 1841 }, 1842 []metav1.TableRow{{Cells: []interface{}{"test2", "1/2", "ContainerWaitingReason", "6", "<unknown>", "<none>", "<none>", "<none>", "<none>"}}}, 1843 }, 1844 } 1845 1846 for i, test := range tests { 1847 rows, err := printPod(&test.pod, printers.GenerateOptions{Wide: true}) 1848 if err != nil { 1849 t.Fatal(err) 1850 } 1851 for i := range rows { 1852 rows[i].Object.Object = nil 1853 } 1854 if !reflect.DeepEqual(test.expect, rows) { 1855 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows)) 1856 } 1857 } 1858 } 1859 1860 func TestPrintPodConditions(t *testing.T) { 1861 runningPod := &api.Pod{ 1862 ObjectMeta: metav1.ObjectMeta{Name: "test1", Labels: map[string]string{"a": "1", "b": "2"}}, 1863 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1864 Status: api.PodStatus{ 1865 Phase: "Running", 1866 ContainerStatuses: []api.ContainerStatus{ 1867 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1868 {RestartCount: 3}, 1869 }, 1870 }, 1871 } 1872 succeededPod := &api.Pod{ 1873 ObjectMeta: metav1.ObjectMeta{Name: "test1", Labels: map[string]string{"a": "1", "b": "2"}}, 1874 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1875 Status: api.PodStatus{ 1876 Phase: "Succeeded", 1877 ContainerStatuses: []api.ContainerStatus{ 1878 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1879 {RestartCount: 3}, 1880 }, 1881 }, 1882 } 1883 failedPod := &api.Pod{ 1884 ObjectMeta: metav1.ObjectMeta{Name: "test2", Labels: map[string]string{"b": "2"}}, 1885 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1886 Status: api.PodStatus{ 1887 Phase: "Failed", 1888 ContainerStatuses: []api.ContainerStatus{ 1889 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1890 {RestartCount: 3}, 1891 }, 1892 }, 1893 } 1894 tests := []struct { 1895 pod *api.Pod 1896 expect []metav1.TableRow 1897 }{ 1898 // Should not have TableRowCondition 1899 { 1900 pod: runningPod, 1901 // Columns: Name, Ready, Reason, Restarts, Age 1902 expect: []metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "Running", "6", "<unknown>"}}}, 1903 }, 1904 // Should have TableRowCondition: podSuccessConditions 1905 { 1906 pod: succeededPod, 1907 expect: []metav1.TableRow{ 1908 { 1909 // Columns: Name, Ready, Reason, Restarts, Age 1910 Cells: []interface{}{"test1", "1/2", "Succeeded", "6", "<unknown>"}, 1911 Conditions: podSuccessConditions, 1912 }, 1913 }, 1914 }, 1915 // Should have TableRowCondition: podFailedCondition 1916 { 1917 pod: failedPod, 1918 expect: []metav1.TableRow{ 1919 { 1920 // Columns: Name, Ready, Reason, Restarts, Age 1921 Cells: []interface{}{"test2", "1/2", "Failed", "6", "<unknown>"}, 1922 Conditions: podFailedConditions, 1923 }, 1924 }, 1925 }, 1926 } 1927 1928 for i, test := range tests { 1929 rows, err := printPod(test.pod, printers.GenerateOptions{}) 1930 if err != nil { 1931 t.Fatal(err) 1932 } 1933 for i := range rows { 1934 rows[i].Object.Object = nil 1935 } 1936 if !reflect.DeepEqual(test.expect, rows) { 1937 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows)) 1938 } 1939 } 1940 } 1941 1942 func TestPrintPodList(t *testing.T) { 1943 tests := []struct { 1944 pods api.PodList 1945 expect []metav1.TableRow 1946 }{ 1947 // Test podList's pod: name, num of containers, restarts, container ready status 1948 { 1949 api.PodList{ 1950 Items: []api.Pod{ 1951 { 1952 ObjectMeta: metav1.ObjectMeta{Name: "test1"}, 1953 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 1954 Status: api.PodStatus{ 1955 Phase: "podPhase", 1956 ContainerStatuses: []api.ContainerStatus{ 1957 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1958 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1959 }, 1960 }, 1961 }, 1962 { 1963 ObjectMeta: metav1.ObjectMeta{Name: "test2"}, 1964 Spec: api.PodSpec{Containers: make([]api.Container, 1)}, 1965 Status: api.PodStatus{ 1966 Phase: "podPhase", 1967 ContainerStatuses: []api.ContainerStatus{ 1968 {Ready: true, RestartCount: 1, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 1969 }, 1970 }, 1971 }, 1972 }, 1973 }, 1974 []metav1.TableRow{{Cells: []interface{}{"test1", "2/2", "podPhase", "6", "<unknown>"}}, {Cells: []interface{}{"test2", "1/1", "podPhase", "1", "<unknown>"}}}, 1975 }, 1976 } 1977 1978 for _, test := range tests { 1979 rows, err := printPodList(&test.pods, printers.GenerateOptions{}) 1980 1981 if err != nil { 1982 t.Fatal(err) 1983 } 1984 for i := range rows { 1985 rows[i].Object.Object = nil 1986 } 1987 if !reflect.DeepEqual(test.expect, rows) { 1988 t.Errorf("mismatch: %s", cmp.Diff(test.expect, rows)) 1989 } 1990 } 1991 } 1992 1993 func TestPrintNonTerminatedPod(t *testing.T) { 1994 tests := []struct { 1995 pod api.Pod 1996 expect []metav1.TableRow 1997 }{ 1998 { 1999 // Test pod phase Running should be printed 2000 api.Pod{ 2001 ObjectMeta: metav1.ObjectMeta{Name: "test1"}, 2002 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 2003 Status: api.PodStatus{ 2004 Phase: api.PodRunning, 2005 ContainerStatuses: []api.ContainerStatus{ 2006 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 2007 {RestartCount: 3}, 2008 }, 2009 }, 2010 }, 2011 // Columns: Name, Ready, Reason, Restarts, Age 2012 []metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "Running", "6", "<unknown>"}}}, 2013 }, 2014 { 2015 // Test pod phase Pending should be printed 2016 api.Pod{ 2017 ObjectMeta: metav1.ObjectMeta{Name: "test2"}, 2018 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 2019 Status: api.PodStatus{ 2020 Phase: api.PodPending, 2021 ContainerStatuses: []api.ContainerStatus{ 2022 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 2023 {RestartCount: 3}, 2024 }, 2025 }, 2026 }, 2027 // Columns: Name, Ready, Reason, Restarts, Age 2028 []metav1.TableRow{{Cells: []interface{}{"test2", "1/2", "Pending", "6", "<unknown>"}}}, 2029 }, 2030 { 2031 // Test pod phase Unknown should be printed 2032 api.Pod{ 2033 ObjectMeta: metav1.ObjectMeta{Name: "test3"}, 2034 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 2035 Status: api.PodStatus{ 2036 Phase: api.PodUnknown, 2037 ContainerStatuses: []api.ContainerStatus{ 2038 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 2039 {RestartCount: 3}, 2040 }, 2041 }, 2042 }, 2043 // Columns: Name, Ready, Reason, Restarts, Age 2044 []metav1.TableRow{{Cells: []interface{}{"test3", "1/2", "Unknown", "6", "<unknown>"}}}, 2045 }, 2046 { 2047 // Test pod phase Succeeded should be printed 2048 api.Pod{ 2049 ObjectMeta: metav1.ObjectMeta{Name: "test4"}, 2050 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 2051 Status: api.PodStatus{ 2052 Phase: api.PodSucceeded, 2053 ContainerStatuses: []api.ContainerStatus{ 2054 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 2055 {RestartCount: 3}, 2056 }, 2057 }, 2058 }, 2059 // Columns: Name, Ready, Reason, Restarts, Age 2060 []metav1.TableRow{ 2061 { 2062 Cells: []interface{}{"test4", "1/2", "Succeeded", "6", "<unknown>"}, 2063 Conditions: podSuccessConditions, 2064 }, 2065 }, 2066 }, 2067 { 2068 // Test pod phase Failed shouldn't be printed 2069 api.Pod{ 2070 ObjectMeta: metav1.ObjectMeta{Name: "test5"}, 2071 Spec: api.PodSpec{Containers: make([]api.Container, 2)}, 2072 Status: api.PodStatus{ 2073 Phase: api.PodFailed, 2074 ContainerStatuses: []api.ContainerStatus{ 2075 {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, 2076 {Ready: true, RestartCount: 3}, 2077 }, 2078 }, 2079 }, 2080 // Columns: Name, Ready, Reason, Restarts, Age 2081 []metav1.TableRow{ 2082 { 2083 Cells: []interface{}{"test5", "1/2", "Failed", "6", "<unknown>"}, 2084 Conditions: podFailedConditions, 2085 }, 2086 }, 2087 }, 2088 } 2089 2090 for i, test := range tests { 2091 rows, err := printPod(&test.pod, printers.GenerateOptions{}) 2092 if err != nil { 2093 t.Fatal(err) 2094 } 2095 for i := range rows { 2096 rows[i].Object.Object = nil 2097 } 2098 if !reflect.DeepEqual(test.expect, rows) { 2099 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows)) 2100 } 2101 } 2102 } 2103 2104 func TestPrintPodTemplate(t *testing.T) { 2105 tests := []struct { 2106 podTemplate api.PodTemplate 2107 options printers.GenerateOptions 2108 expected []metav1.TableRow 2109 }{ 2110 // Test basic pod template with no containers. 2111 { 2112 podTemplate: api.PodTemplate{ 2113 ObjectMeta: metav1.ObjectMeta{Name: "pod-template-1"}, 2114 Template: api.PodTemplateSpec{ 2115 ObjectMeta: metav1.ObjectMeta{Name: "pod-template-1"}, 2116 Spec: api.PodSpec{ 2117 Containers: []api.Container{}, 2118 }, 2119 }, 2120 }, 2121 2122 options: printers.GenerateOptions{}, 2123 // Columns: Name, Containers, Images, Pod Labels 2124 expected: []metav1.TableRow{{Cells: []interface{}{"pod-template-1", "", "", "<none>"}}}, 2125 }, 2126 // Test basic pod template with two containers. 2127 { 2128 podTemplate: api.PodTemplate{ 2129 ObjectMeta: metav1.ObjectMeta{Name: "pod-template-2"}, 2130 Template: api.PodTemplateSpec{ 2131 ObjectMeta: metav1.ObjectMeta{Name: "pod-template-2"}, 2132 Spec: api.PodSpec{ 2133 Containers: []api.Container{ 2134 { 2135 Name: "fake-container1", 2136 Image: "fake-image1", 2137 }, 2138 { 2139 Name: "fake-container2", 2140 Image: "fake-image2", 2141 }, 2142 }, 2143 }, 2144 }, 2145 }, 2146 2147 options: printers.GenerateOptions{}, 2148 // Columns: Name, Containers, Images, Pod Labels 2149 expected: []metav1.TableRow{{Cells: []interface{}{"pod-template-2", "fake-container1,fake-container2", "fake-image1,fake-image2", "<none>"}}}, 2150 }, 2151 // Test basic pod template with pod labels 2152 { 2153 podTemplate: api.PodTemplate{ 2154 ObjectMeta: metav1.ObjectMeta{Name: "pod-template-3"}, 2155 Template: api.PodTemplateSpec{ 2156 ObjectMeta: metav1.ObjectMeta{ 2157 Name: "pod-template-3", 2158 Labels: map[string]string{"foo": "bar"}, 2159 }, 2160 Spec: api.PodSpec{ 2161 Containers: []api.Container{}, 2162 }, 2163 }, 2164 }, 2165 2166 options: printers.GenerateOptions{}, 2167 // Columns: Name, Containers, Images, Pod Labels 2168 expected: []metav1.TableRow{{Cells: []interface{}{"pod-template-3", "", "", "foo=bar"}}}, 2169 }, 2170 } 2171 2172 for i, test := range tests { 2173 rows, err := printPodTemplate(&test.podTemplate, test.options) 2174 if err != nil { 2175 t.Fatal(err) 2176 } 2177 for i := range rows { 2178 rows[i].Object.Object = nil 2179 } 2180 if !reflect.DeepEqual(test.expected, rows) { 2181 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 2182 } 2183 } 2184 } 2185 2186 func TestPrintPodTemplateList(t *testing.T) { 2187 2188 templateList := api.PodTemplateList{ 2189 Items: []api.PodTemplate{ 2190 { 2191 ObjectMeta: metav1.ObjectMeta{Name: "pod-template-1"}, 2192 Template: api.PodTemplateSpec{ 2193 ObjectMeta: metav1.ObjectMeta{ 2194 Name: "pod-template-2", 2195 Labels: map[string]string{"foo": "bar"}, 2196 }, 2197 Spec: api.PodSpec{ 2198 Containers: []api.Container{}, 2199 }, 2200 }, 2201 }, 2202 { 2203 ObjectMeta: metav1.ObjectMeta{Name: "pod-template-2"}, 2204 Template: api.PodTemplateSpec{ 2205 ObjectMeta: metav1.ObjectMeta{ 2206 Name: "pod-template-2", 2207 Labels: map[string]string{"a": "b"}, 2208 }, 2209 Spec: api.PodSpec{ 2210 Containers: []api.Container{}, 2211 }, 2212 }, 2213 }, 2214 }, 2215 } 2216 2217 // Columns: Name, Containers, Images, Pod Labels 2218 expectedRows := []metav1.TableRow{ 2219 {Cells: []interface{}{"pod-template-1", "", "", "foo=bar"}}, 2220 {Cells: []interface{}{"pod-template-2", "", "", "a=b"}}, 2221 } 2222 2223 rows, err := printPodTemplateList(&templateList, printers.GenerateOptions{}) 2224 if err != nil { 2225 t.Fatalf("Error printing pod template list: %#v", err) 2226 } 2227 for i := range rows { 2228 rows[i].Object.Object = nil 2229 } 2230 if !reflect.DeepEqual(expectedRows, rows) { 2231 t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) 2232 } 2233 } 2234 2235 type stringTestList []struct { 2236 name, got, exp string 2237 } 2238 2239 func TestTranslateTimestampSince(t *testing.T) { 2240 tl := stringTestList{ 2241 {"a while from now", translateTimestampSince(metav1.Time{Time: time.Now().Add(2.1e9)}), "<invalid>"}, 2242 {"almost now", translateTimestampSince(metav1.Time{Time: time.Now().Add(1.9e9)}), "0s"}, 2243 {"now", translateTimestampSince(metav1.Time{Time: time.Now()}), "0s"}, 2244 {"unknown", translateTimestampSince(metav1.Time{}), "<unknown>"}, 2245 {"30 seconds ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-3e10)}), "30s"}, 2246 {"5 minutes ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-3e11)}), "5m"}, 2247 {"an hour ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-6e12)}), "100m"}, 2248 {"2 days ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}), "2d"}, 2249 {"months ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -90)}), "90d"}, 2250 {"10 years ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(-10, 0, 0)}), "10y"}, 2251 } 2252 for _, test := range tl { 2253 if test.got != test.exp { 2254 t.Errorf("On %v, expected '%v', but got '%v'", 2255 test.name, test.exp, test.got) 2256 } 2257 } 2258 } 2259 2260 func TestTranslateTimestampUntil(t *testing.T) { 2261 // Since this method compares the time with time.Now() internally, 2262 // small buffers of 0.1 seconds are added on comparing times to consider method call overhead. 2263 // Otherwise, the output strings become shorter than expected. 2264 const buf = 1e8 2265 tl := stringTestList{ 2266 {"a while ago", translateTimestampUntil(metav1.Time{Time: time.Now().Add(-2.1e9)}), "<invalid>"}, 2267 {"almost now", translateTimestampUntil(metav1.Time{Time: time.Now().Add(-1.9e9)}), "0s"}, 2268 {"now", translateTimestampUntil(metav1.Time{Time: time.Now()}), "0s"}, 2269 {"unknown", translateTimestampUntil(metav1.Time{}), "<unknown>"}, 2270 {"in 30 seconds", translateTimestampUntil(metav1.Time{Time: time.Now().Add(3e10 + buf)}), "30s"}, 2271 {"in 5 minutes", translateTimestampUntil(metav1.Time{Time: time.Now().Add(3e11 + buf)}), "5m"}, 2272 {"in an hour", translateTimestampUntil(metav1.Time{Time: time.Now().Add(6e12 + buf)}), "100m"}, 2273 {"in 2 days", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, 2).Add(buf)}), "2d"}, 2274 {"in months", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, 90).Add(buf)}), "90d"}, 2275 {"in 10 years", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(10, 0, 0).Add(buf)}), "10y"}, 2276 } 2277 for _, test := range tl { 2278 if test.got != test.exp { 2279 t.Errorf("On %v, expected '%v', but got '%v'", 2280 test.name, test.exp, test.got) 2281 } 2282 } 2283 } 2284 2285 func TestPrintDeployment(t *testing.T) { 2286 2287 testDeployment := apps.Deployment{ 2288 ObjectMeta: metav1.ObjectMeta{ 2289 Name: "test1", 2290 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 2291 }, 2292 Spec: apps.DeploymentSpec{ 2293 Replicas: 5, 2294 Template: api.PodTemplateSpec{ 2295 Spec: api.PodSpec{ 2296 Containers: []api.Container{ 2297 { 2298 Name: "fake-container1", 2299 Image: "fake-image1", 2300 }, 2301 { 2302 Name: "fake-container2", 2303 Image: "fake-image2", 2304 }, 2305 }, 2306 }, 2307 }, 2308 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 2309 }, 2310 Status: apps.DeploymentStatus{ 2311 Replicas: 10, 2312 UpdatedReplicas: 2, 2313 AvailableReplicas: 1, 2314 UnavailableReplicas: 4, 2315 }, 2316 } 2317 2318 tests := []struct { 2319 deployment apps.Deployment 2320 options printers.GenerateOptions 2321 expected []metav1.TableRow 2322 }{ 2323 // Test Deployment with no generate options. 2324 { 2325 deployment: testDeployment, 2326 options: printers.GenerateOptions{}, 2327 // Columns: Name, ReadyReplicas, UpdatedReplicas, AvailableReplicas, Age 2328 expected: []metav1.TableRow{{Cells: []interface{}{"test1", "0/5", int64(2), int64(1), "0s"}}}, 2329 }, 2330 // Test generate options: Wide. 2331 { 2332 deployment: testDeployment, 2333 options: printers.GenerateOptions{Wide: true}, 2334 // Columns: Name, ReadyReplicas, UpdatedReplicas, AvailableReplicas, Age, Containers, Images, Selectors 2335 expected: []metav1.TableRow{{Cells: []interface{}{"test1", "0/5", int64(2), int64(1), "0s", "fake-container1,fake-container2", "fake-image1,fake-image2", "foo=bar"}}}, 2336 }, 2337 } 2338 2339 for i, test := range tests { 2340 rows, err := printDeployment(&test.deployment, test.options) 2341 if err != nil { 2342 t.Fatal(err) 2343 } 2344 for i := range rows { 2345 rows[i].Object.Object = nil 2346 } 2347 if !reflect.DeepEqual(test.expected, rows) { 2348 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 2349 } 2350 } 2351 } 2352 2353 func TestPrintDaemonSet(t *testing.T) { 2354 2355 testDaemonSet := apps.DaemonSet{ 2356 ObjectMeta: metav1.ObjectMeta{ 2357 Name: "test1", 2358 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 2359 }, 2360 Spec: apps.DaemonSetSpec{ 2361 Template: api.PodTemplateSpec{ 2362 Spec: api.PodSpec{ 2363 Containers: []api.Container{ 2364 { 2365 Name: "fake-container1", 2366 Image: "fake-image1", 2367 }, 2368 { 2369 Name: "fake-container2", 2370 Image: "fake-image2", 2371 }, 2372 }, 2373 }, 2374 }, 2375 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 2376 }, 2377 Status: apps.DaemonSetStatus{ 2378 CurrentNumberScheduled: 2, 2379 DesiredNumberScheduled: 3, 2380 NumberReady: 1, 2381 UpdatedNumberScheduled: 2, 2382 NumberAvailable: 0, 2383 }, 2384 } 2385 2386 tests := []struct { 2387 daemonSet apps.DaemonSet 2388 options printers.GenerateOptions 2389 expected []metav1.TableRow 2390 }{ 2391 // Test generate daemon set with no generate options. 2392 { 2393 daemonSet: testDaemonSet, 2394 options: printers.GenerateOptions{}, 2395 // Columns: Name, Num Desired, Num Current, Num Ready, Num Updated, Num Available, Selectors, Age 2396 expected: []metav1.TableRow{{Cells: []interface{}{"test1", int64(3), int64(2), int64(1), int64(2), int64(0), "<none>", "0s"}}}, 2397 }, 2398 // Test generate daemon set with "Wide" generate options. 2399 { 2400 daemonSet: testDaemonSet, 2401 options: printers.GenerateOptions{Wide: true}, 2402 // Columns: Name, Num Desired, Num Current, Num Ready, Num Updated, Num Available, Node Selectors, Age, Containers, Images, Labels 2403 expected: []metav1.TableRow{{Cells: []interface{}{"test1", int64(3), int64(2), int64(1), int64(2), int64(0), "<none>", "0s", "fake-container1,fake-container2", "fake-image1,fake-image2", "foo=bar"}}}, 2404 }, 2405 } 2406 2407 for i, test := range tests { 2408 rows, err := printDaemonSet(&test.daemonSet, test.options) 2409 if err != nil { 2410 t.Fatal(err) 2411 } 2412 for i := range rows { 2413 rows[i].Object.Object = nil 2414 } 2415 if !reflect.DeepEqual(test.expected, rows) { 2416 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 2417 } 2418 } 2419 } 2420 2421 func TestPrintDaemonSetList(t *testing.T) { 2422 2423 daemonSetList := apps.DaemonSetList{ 2424 Items: []apps.DaemonSet{ 2425 { 2426 ObjectMeta: metav1.ObjectMeta{ 2427 Name: "daemonset1", 2428 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 2429 }, 2430 Spec: apps.DaemonSetSpec{ 2431 Template: api.PodTemplateSpec{ 2432 Spec: api.PodSpec{ 2433 Containers: []api.Container{ 2434 { 2435 Name: "fake-container1", 2436 Image: "fake-image1", 2437 }, 2438 { 2439 Name: "fake-container2", 2440 Image: "fake-image2", 2441 }, 2442 }, 2443 }, 2444 }, 2445 }, 2446 Status: apps.DaemonSetStatus{ 2447 CurrentNumberScheduled: 2, 2448 DesiredNumberScheduled: 3, 2449 NumberReady: 1, 2450 UpdatedNumberScheduled: 2, 2451 NumberAvailable: 0, 2452 }, 2453 }, 2454 { 2455 ObjectMeta: metav1.ObjectMeta{ 2456 Name: "daemonset2", 2457 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 2458 }, 2459 Spec: apps.DaemonSetSpec{ 2460 Template: api.PodTemplateSpec{}, 2461 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 2462 }, 2463 Status: apps.DaemonSetStatus{ 2464 CurrentNumberScheduled: 4, 2465 DesiredNumberScheduled: 2, 2466 NumberReady: 9, 2467 UpdatedNumberScheduled: 3, 2468 NumberAvailable: 3, 2469 }, 2470 }, 2471 }, 2472 } 2473 2474 // Columns: Name, Num Desired, Num Current, Num Ready, Num Updated, Num Available, Selectors, Age 2475 expectedRows := []metav1.TableRow{ 2476 {Cells: []interface{}{"daemonset1", int64(3), int64(2), int64(1), int64(2), int64(0), "<none>", "0s"}}, 2477 {Cells: []interface{}{"daemonset2", int64(2), int64(4), int64(9), int64(3), int64(3), "<none>", "0s"}}, 2478 } 2479 2480 rows, err := printDaemonSetList(&daemonSetList, printers.GenerateOptions{}) 2481 if err != nil { 2482 t.Fatalf("Error printing daemon set list: %#v", err) 2483 } 2484 for i := range rows { 2485 rows[i].Object.Object = nil 2486 } 2487 if !reflect.DeepEqual(expectedRows, rows) { 2488 t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) 2489 } 2490 } 2491 2492 func TestPrintJob(t *testing.T) { 2493 now := time.Now() 2494 completions := int32(2) 2495 tests := []struct { 2496 job batch.Job 2497 options printers.GenerateOptions 2498 expected []metav1.TableRow 2499 }{ 2500 { 2501 // Generate table rows for Job with no generate options. 2502 job: batch.Job{ 2503 ObjectMeta: metav1.ObjectMeta{ 2504 Name: "job1", 2505 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 2506 }, 2507 Spec: batch.JobSpec{ 2508 Completions: &completions, 2509 Template: api.PodTemplateSpec{ 2510 Spec: api.PodSpec{ 2511 Containers: []api.Container{ 2512 { 2513 Name: "fake-job-container1", 2514 Image: "fake-job-image1", 2515 }, 2516 { 2517 Name: "fake-job-container2", 2518 Image: "fake-job-image2", 2519 }, 2520 }, 2521 }, 2522 }, 2523 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-lable-value"}}, 2524 }, 2525 Status: batch.JobStatus{ 2526 Succeeded: 1, 2527 }, 2528 }, 2529 options: printers.GenerateOptions{}, 2530 // Columns: Name, Completions, Duration, Age 2531 expected: []metav1.TableRow{{Cells: []interface{}{"job1", "1/2", "", "0s"}}}, 2532 }, 2533 // Generate table rows for Job with generate options "Wide". 2534 { 2535 job: batch.Job{ 2536 ObjectMeta: metav1.ObjectMeta{ 2537 Name: "job1", 2538 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 2539 }, 2540 Spec: batch.JobSpec{ 2541 Completions: &completions, 2542 Template: api.PodTemplateSpec{ 2543 Spec: api.PodSpec{ 2544 Containers: []api.Container{ 2545 { 2546 Name: "fake-job-container1", 2547 Image: "fake-job-image1", 2548 }, 2549 { 2550 Name: "fake-job-container2", 2551 Image: "fake-job-image2", 2552 }, 2553 }, 2554 }, 2555 }, 2556 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-label-value"}}, 2557 }, 2558 Status: batch.JobStatus{ 2559 Succeeded: 1, 2560 }, 2561 }, 2562 options: printers.GenerateOptions{Wide: true}, 2563 // Columns: Name, Completions, Duration, Age, Containers, Images, Selectors 2564 expected: []metav1.TableRow{ 2565 { 2566 Cells: []interface{}{"job1", "1/2", "", "0s", "fake-job-container1,fake-job-container2", "fake-job-image1,fake-job-image2", "job-label=job-label-value"}, 2567 }, 2568 }, 2569 }, 2570 // Job with ten-year age. 2571 { 2572 job: batch.Job{ 2573 ObjectMeta: metav1.ObjectMeta{ 2574 Name: "job2", 2575 CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, 2576 }, 2577 Spec: batch.JobSpec{ 2578 Completions: nil, 2579 }, 2580 Status: batch.JobStatus{ 2581 Succeeded: 0, 2582 }, 2583 }, 2584 options: printers.GenerateOptions{}, 2585 // Columns: Name, Completions, Duration, Age 2586 expected: []metav1.TableRow{{Cells: []interface{}{"job2", "0/1", "", "10y"}}}, 2587 }, 2588 // Job with duration. 2589 { 2590 job: batch.Job{ 2591 ObjectMeta: metav1.ObjectMeta{ 2592 Name: "job3", 2593 CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, 2594 }, 2595 Spec: batch.JobSpec{ 2596 Completions: nil, 2597 }, 2598 Status: batch.JobStatus{ 2599 Succeeded: 0, 2600 StartTime: &metav1.Time{Time: now.Add(time.Minute)}, 2601 CompletionTime: &metav1.Time{Time: now.Add(31 * time.Minute)}, 2602 }, 2603 }, 2604 options: printers.GenerateOptions{}, 2605 // Columns: Name, Completions, Duration, Age 2606 expected: []metav1.TableRow{{Cells: []interface{}{"job3", "0/1", "30m", "10y"}}}, 2607 }, 2608 { 2609 job: batch.Job{ 2610 ObjectMeta: metav1.ObjectMeta{ 2611 Name: "job4", 2612 CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, 2613 }, 2614 Spec: batch.JobSpec{ 2615 Completions: nil, 2616 }, 2617 Status: batch.JobStatus{ 2618 Succeeded: 0, 2619 StartTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)}, 2620 }, 2621 }, 2622 options: printers.GenerateOptions{}, 2623 // Columns: Name, Completions, Duration, Age 2624 expected: []metav1.TableRow{{Cells: []interface{}{"job4", "0/1", "20m", "10y"}}}, 2625 }, 2626 } 2627 2628 for i, test := range tests { 2629 rows, err := printJob(&test.job, test.options) 2630 if err != nil { 2631 t.Fatal(err) 2632 } 2633 for i := range rows { 2634 rows[i].Object.Object = nil 2635 } 2636 if !reflect.DeepEqual(test.expected, rows) { 2637 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 2638 } 2639 } 2640 } 2641 2642 func TestPrintJobList(t *testing.T) { 2643 completions := int32(2) 2644 jobList := batch.JobList{ 2645 Items: []batch.Job{ 2646 { 2647 ObjectMeta: metav1.ObjectMeta{ 2648 Name: "job1", 2649 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 2650 }, 2651 Spec: batch.JobSpec{ 2652 Completions: &completions, 2653 Template: api.PodTemplateSpec{ 2654 Spec: api.PodSpec{ 2655 Containers: []api.Container{ 2656 { 2657 Name: "fake-job-container1", 2658 Image: "fake-job-image1", 2659 }, 2660 { 2661 Name: "fake-job-container2", 2662 Image: "fake-job-image2", 2663 }, 2664 }, 2665 }, 2666 }, 2667 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-lable-value"}}, 2668 }, 2669 Status: batch.JobStatus{ 2670 Succeeded: 1, 2671 }, 2672 }, 2673 { 2674 ObjectMeta: metav1.ObjectMeta{ 2675 Name: "job2", 2676 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 2677 }, 2678 Spec: batch.JobSpec{ 2679 Completions: &completions, 2680 Template: api.PodTemplateSpec{ 2681 Spec: api.PodSpec{ 2682 Containers: []api.Container{ 2683 { 2684 Name: "fake-job-container1", 2685 Image: "fake-job-image1", 2686 }, 2687 { 2688 Name: "fake-job-container2", 2689 Image: "fake-job-image2", 2690 }, 2691 }, 2692 }, 2693 }, 2694 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-lable-value"}}, 2695 }, 2696 Status: batch.JobStatus{ 2697 Succeeded: 2, 2698 StartTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)}, 2699 }, 2700 }, 2701 }, 2702 } 2703 2704 // Columns: Name, Completions, Duration, Age 2705 expectedRows := []metav1.TableRow{ 2706 {Cells: []interface{}{"job1", "1/2", "", "0s"}}, 2707 {Cells: []interface{}{"job2", "2/2", "20m", "0s"}}, 2708 } 2709 2710 rows, err := printJobList(&jobList, printers.GenerateOptions{}) 2711 if err != nil { 2712 t.Fatalf("Error printing job list: %#v", err) 2713 } 2714 for i := range rows { 2715 rows[i].Object.Object = nil 2716 } 2717 if !reflect.DeepEqual(expectedRows, rows) { 2718 t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) 2719 } 2720 } 2721 2722 func TestPrintHPA(t *testing.T) { 2723 minReplicasVal := int32(2) 2724 targetUtilizationVal := int32(80) 2725 currentUtilizationVal := int32(50) 2726 metricLabelSelector, err := metav1.ParseToLabelSelector("label=value") 2727 if err != nil { 2728 t.Errorf("unable to parse label selector: %v", err) 2729 } 2730 tests := []struct { 2731 hpa autoscaling.HorizontalPodAutoscaler 2732 expected []metav1.TableRow 2733 }{ 2734 // minReplicas unset 2735 { 2736 hpa: autoscaling.HorizontalPodAutoscaler{ 2737 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 2738 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 2739 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 2740 Name: "some-rc", 2741 Kind: "ReplicationController", 2742 }, 2743 MaxReplicas: 10, 2744 }, 2745 Status: autoscaling.HorizontalPodAutoscalerStatus{ 2746 CurrentReplicas: 4, 2747 DesiredReplicas: 5, 2748 }, 2749 }, 2750 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 2751 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<none>", "<unset>", int64(10), int64(4), "<unknown>"}}}, 2752 }, 2753 // external source type, target average value (no current) 2754 { 2755 hpa: autoscaling.HorizontalPodAutoscaler{ 2756 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 2757 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 2758 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 2759 Name: "some-rc", 2760 Kind: "ReplicationController", 2761 }, 2762 MinReplicas: &minReplicasVal, 2763 MaxReplicas: 10, 2764 Metrics: []autoscaling.MetricSpec{ 2765 { 2766 Type: autoscaling.ExternalMetricSourceType, 2767 External: &autoscaling.ExternalMetricSource{ 2768 Metric: autoscaling.MetricIdentifier{ 2769 Name: "some-external-metric", 2770 Selector: metricLabelSelector, 2771 }, 2772 Target: autoscaling.MetricTarget{ 2773 Type: autoscaling.AverageValueMetricType, 2774 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), 2775 }, 2776 }, 2777 }, 2778 }, 2779 }, 2780 Status: autoscaling.HorizontalPodAutoscalerStatus{ 2781 CurrentReplicas: 4, 2782 DesiredReplicas: 5, 2783 }, 2784 }, 2785 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 2786 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/100m (avg)", "2", int64(10), int64(4), "<unknown>"}}}, 2787 }, 2788 // external source type, target average value 2789 { 2790 hpa: autoscaling.HorizontalPodAutoscaler{ 2791 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 2792 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 2793 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 2794 Name: "some-rc", 2795 Kind: "ReplicationController", 2796 }, 2797 MinReplicas: &minReplicasVal, 2798 MaxReplicas: 10, 2799 Metrics: []autoscaling.MetricSpec{ 2800 { 2801 Type: autoscaling.ExternalMetricSourceType, 2802 External: &autoscaling.ExternalMetricSource{ 2803 Metric: autoscaling.MetricIdentifier{ 2804 Name: "some-external-metric", 2805 Selector: metricLabelSelector, 2806 }, 2807 Target: autoscaling.MetricTarget{ 2808 Type: autoscaling.AverageValueMetricType, 2809 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), 2810 }, 2811 }, 2812 }, 2813 }, 2814 }, 2815 Status: autoscaling.HorizontalPodAutoscalerStatus{ 2816 CurrentReplicas: 4, 2817 DesiredReplicas: 5, 2818 CurrentMetrics: []autoscaling.MetricStatus{ 2819 { 2820 Type: autoscaling.ExternalMetricSourceType, 2821 External: &autoscaling.ExternalMetricStatus{ 2822 Metric: autoscaling.MetricIdentifier{ 2823 Name: "some-external-metric", 2824 Selector: metricLabelSelector, 2825 }, 2826 Current: autoscaling.MetricValueStatus{ 2827 AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), 2828 }, 2829 }, 2830 }, 2831 }, 2832 }, 2833 }, 2834 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 2835 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m (avg)", "2", int64(10), int64(4), "<unknown>"}}}, 2836 }, 2837 // external source type, target value (no current) 2838 { 2839 hpa: autoscaling.HorizontalPodAutoscaler{ 2840 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 2841 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 2842 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 2843 Name: "some-rc", 2844 Kind: "ReplicationController", 2845 }, 2846 MinReplicas: &minReplicasVal, 2847 MaxReplicas: 10, 2848 Metrics: []autoscaling.MetricSpec{ 2849 { 2850 Type: autoscaling.ExternalMetricSourceType, 2851 External: &autoscaling.ExternalMetricSource{ 2852 Metric: autoscaling.MetricIdentifier{ 2853 Name: "some-service-metric", 2854 Selector: metricLabelSelector, 2855 }, 2856 Target: autoscaling.MetricTarget{ 2857 Type: autoscaling.ValueMetricType, 2858 Value: resource.NewMilliQuantity(100, resource.DecimalSI), 2859 }, 2860 }, 2861 }, 2862 }, 2863 }, 2864 Status: autoscaling.HorizontalPodAutoscalerStatus{ 2865 CurrentReplicas: 4, 2866 DesiredReplicas: 5, 2867 }, 2868 }, 2869 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 2870 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/100m", "2", int64(10), int64(4), "<unknown>"}}}, 2871 }, 2872 // external source type, target value 2873 { 2874 hpa: autoscaling.HorizontalPodAutoscaler{ 2875 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 2876 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 2877 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 2878 Name: "some-rc", 2879 Kind: "ReplicationController", 2880 }, 2881 MinReplicas: &minReplicasVal, 2882 MaxReplicas: 10, 2883 Metrics: []autoscaling.MetricSpec{ 2884 { 2885 Type: autoscaling.ExternalMetricSourceType, 2886 External: &autoscaling.ExternalMetricSource{ 2887 Metric: autoscaling.MetricIdentifier{ 2888 Name: "some-external-metric", 2889 Selector: metricLabelSelector, 2890 }, 2891 Target: autoscaling.MetricTarget{ 2892 Type: autoscaling.ValueMetricType, 2893 Value: resource.NewMilliQuantity(100, resource.DecimalSI), 2894 }, 2895 }, 2896 }, 2897 }, 2898 }, 2899 Status: autoscaling.HorizontalPodAutoscalerStatus{ 2900 CurrentReplicas: 4, 2901 DesiredReplicas: 5, 2902 CurrentMetrics: []autoscaling.MetricStatus{ 2903 { 2904 Type: autoscaling.ExternalMetricSourceType, 2905 External: &autoscaling.ExternalMetricStatus{ 2906 Metric: autoscaling.MetricIdentifier{ 2907 Name: "some-external-metric", 2908 }, 2909 Current: autoscaling.MetricValueStatus{ 2910 Value: resource.NewMilliQuantity(50, resource.DecimalSI), 2911 }, 2912 }, 2913 }, 2914 }, 2915 }, 2916 }, 2917 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 2918 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), "<unknown>"}}}, 2919 }, 2920 // pods source type (no current) 2921 { 2922 hpa: autoscaling.HorizontalPodAutoscaler{ 2923 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 2924 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 2925 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 2926 Name: "some-rc", 2927 Kind: "ReplicationController", 2928 }, 2929 MinReplicas: &minReplicasVal, 2930 MaxReplicas: 10, 2931 Metrics: []autoscaling.MetricSpec{ 2932 { 2933 Type: autoscaling.PodsMetricSourceType, 2934 Pods: &autoscaling.PodsMetricSource{ 2935 Metric: autoscaling.MetricIdentifier{ 2936 Name: "some-pods-metric", 2937 }, 2938 Target: autoscaling.MetricTarget{ 2939 Type: autoscaling.AverageValueMetricType, 2940 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), 2941 }, 2942 }, 2943 }, 2944 }, 2945 }, 2946 Status: autoscaling.HorizontalPodAutoscalerStatus{ 2947 CurrentReplicas: 4, 2948 DesiredReplicas: 5, 2949 }, 2950 }, 2951 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 2952 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/100m", "2", int64(10), int64(4), "<unknown>"}}}, 2953 }, 2954 // pods source type 2955 { 2956 hpa: autoscaling.HorizontalPodAutoscaler{ 2957 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 2958 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 2959 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 2960 Name: "some-rc", 2961 Kind: "ReplicationController", 2962 }, 2963 MinReplicas: &minReplicasVal, 2964 MaxReplicas: 10, 2965 Metrics: []autoscaling.MetricSpec{ 2966 { 2967 Type: autoscaling.PodsMetricSourceType, 2968 Pods: &autoscaling.PodsMetricSource{ 2969 Metric: autoscaling.MetricIdentifier{ 2970 Name: "some-pods-metric", 2971 }, 2972 Target: autoscaling.MetricTarget{ 2973 Type: autoscaling.AverageValueMetricType, 2974 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), 2975 }, 2976 }, 2977 }, 2978 }, 2979 }, 2980 Status: autoscaling.HorizontalPodAutoscalerStatus{ 2981 CurrentReplicas: 4, 2982 DesiredReplicas: 5, 2983 CurrentMetrics: []autoscaling.MetricStatus{ 2984 { 2985 Type: autoscaling.PodsMetricSourceType, 2986 Pods: &autoscaling.PodsMetricStatus{ 2987 Metric: autoscaling.MetricIdentifier{ 2988 Name: "some-pods-metric", 2989 }, 2990 Current: autoscaling.MetricValueStatus{ 2991 AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), 2992 }, 2993 }, 2994 }, 2995 }, 2996 }, 2997 }, 2998 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 2999 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), "<unknown>"}}}, 3000 }, 3001 // object source type (no current) 3002 { 3003 hpa: autoscaling.HorizontalPodAutoscaler{ 3004 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 3005 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 3006 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 3007 Name: "some-rc", 3008 Kind: "ReplicationController", 3009 }, 3010 MinReplicas: &minReplicasVal, 3011 MaxReplicas: 10, 3012 Metrics: []autoscaling.MetricSpec{ 3013 { 3014 Type: autoscaling.ObjectMetricSourceType, 3015 Object: &autoscaling.ObjectMetricSource{ 3016 DescribedObject: autoscaling.CrossVersionObjectReference{ 3017 Name: "some-service", 3018 Kind: "Service", 3019 }, 3020 Metric: autoscaling.MetricIdentifier{ 3021 Name: "some-service-metric", 3022 }, 3023 Target: autoscaling.MetricTarget{ 3024 Type: autoscaling.ValueMetricType, 3025 Value: resource.NewMilliQuantity(100, resource.DecimalSI), 3026 }, 3027 }, 3028 }, 3029 }, 3030 }, 3031 Status: autoscaling.HorizontalPodAutoscalerStatus{ 3032 CurrentReplicas: 4, 3033 DesiredReplicas: 5, 3034 }, 3035 }, 3036 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 3037 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/100m", "2", int64(10), int64(4), "<unknown>"}}}, 3038 }, 3039 // object source type 3040 { 3041 hpa: autoscaling.HorizontalPodAutoscaler{ 3042 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 3043 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 3044 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 3045 Name: "some-rc", 3046 Kind: "ReplicationController", 3047 }, 3048 MinReplicas: &minReplicasVal, 3049 MaxReplicas: 10, 3050 Metrics: []autoscaling.MetricSpec{ 3051 { 3052 Type: autoscaling.ObjectMetricSourceType, 3053 Object: &autoscaling.ObjectMetricSource{ 3054 DescribedObject: autoscaling.CrossVersionObjectReference{ 3055 Name: "some-service", 3056 Kind: "Service", 3057 }, 3058 Metric: autoscaling.MetricIdentifier{ 3059 Name: "some-service-metric", 3060 }, 3061 Target: autoscaling.MetricTarget{ 3062 Type: autoscaling.ValueMetricType, 3063 Value: resource.NewMilliQuantity(100, resource.DecimalSI), 3064 }, 3065 }, 3066 }, 3067 }, 3068 }, 3069 Status: autoscaling.HorizontalPodAutoscalerStatus{ 3070 CurrentReplicas: 4, 3071 DesiredReplicas: 5, 3072 CurrentMetrics: []autoscaling.MetricStatus{ 3073 { 3074 Type: autoscaling.ObjectMetricSourceType, 3075 Object: &autoscaling.ObjectMetricStatus{ 3076 DescribedObject: autoscaling.CrossVersionObjectReference{ 3077 Name: "some-service", 3078 Kind: "Service", 3079 }, 3080 Metric: autoscaling.MetricIdentifier{ 3081 Name: "some-service-metric", 3082 }, 3083 Current: autoscaling.MetricValueStatus{ 3084 Value: resource.NewMilliQuantity(50, resource.DecimalSI), 3085 }, 3086 }, 3087 }, 3088 }, 3089 }, 3090 }, 3091 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 3092 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), "<unknown>"}}}, 3093 }, 3094 // resource source type, targetVal (no current) 3095 { 3096 hpa: autoscaling.HorizontalPodAutoscaler{ 3097 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 3098 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 3099 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 3100 Name: "some-rc", 3101 Kind: "ReplicationController", 3102 }, 3103 MinReplicas: &minReplicasVal, 3104 MaxReplicas: 10, 3105 Metrics: []autoscaling.MetricSpec{ 3106 { 3107 Type: autoscaling.ResourceMetricSourceType, 3108 Resource: &autoscaling.ResourceMetricSource{ 3109 Name: api.ResourceCPU, 3110 Target: autoscaling.MetricTarget{ 3111 Type: autoscaling.AverageValueMetricType, 3112 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), 3113 }, 3114 }, 3115 }, 3116 }, 3117 }, 3118 Status: autoscaling.HorizontalPodAutoscalerStatus{ 3119 CurrentReplicas: 4, 3120 DesiredReplicas: 5, 3121 }, 3122 }, 3123 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 3124 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/100m", "2", int64(10), int64(4), "<unknown>"}}}, 3125 }, 3126 // resource source type, targetVal 3127 { 3128 hpa: autoscaling.HorizontalPodAutoscaler{ 3129 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 3130 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 3131 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 3132 Name: "some-rc", 3133 Kind: "ReplicationController", 3134 }, 3135 MinReplicas: &minReplicasVal, 3136 MaxReplicas: 10, 3137 Metrics: []autoscaling.MetricSpec{ 3138 { 3139 Type: autoscaling.ResourceMetricSourceType, 3140 Resource: &autoscaling.ResourceMetricSource{ 3141 Name: api.ResourceCPU, 3142 Target: autoscaling.MetricTarget{ 3143 Type: autoscaling.AverageValueMetricType, 3144 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), 3145 }, 3146 }, 3147 }, 3148 }, 3149 }, 3150 Status: autoscaling.HorizontalPodAutoscalerStatus{ 3151 CurrentReplicas: 4, 3152 DesiredReplicas: 5, 3153 CurrentMetrics: []autoscaling.MetricStatus{ 3154 { 3155 Type: autoscaling.ResourceMetricSourceType, 3156 Resource: &autoscaling.ResourceMetricStatus{ 3157 Name: api.ResourceCPU, 3158 Current: autoscaling.MetricValueStatus{ 3159 AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), 3160 }, 3161 }, 3162 }, 3163 }, 3164 }, 3165 }, 3166 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 3167 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), "<unknown>"}}}, 3168 }, 3169 // resource source type, targetUtil (no current) 3170 { 3171 hpa: autoscaling.HorizontalPodAutoscaler{ 3172 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 3173 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 3174 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 3175 Name: "some-rc", 3176 Kind: "ReplicationController", 3177 }, 3178 MinReplicas: &minReplicasVal, 3179 MaxReplicas: 10, 3180 Metrics: []autoscaling.MetricSpec{ 3181 { 3182 Type: autoscaling.ResourceMetricSourceType, 3183 Resource: &autoscaling.ResourceMetricSource{ 3184 Name: api.ResourceCPU, 3185 Target: autoscaling.MetricTarget{ 3186 Type: autoscaling.UtilizationMetricType, 3187 AverageUtilization: &targetUtilizationVal, 3188 }, 3189 }, 3190 }, 3191 }, 3192 }, 3193 Status: autoscaling.HorizontalPodAutoscalerStatus{ 3194 CurrentReplicas: 4, 3195 DesiredReplicas: 5, 3196 }, 3197 }, 3198 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 3199 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/80%", "2", int64(10), int64(4), "<unknown>"}}}, 3200 }, 3201 // resource source type, targetUtil 3202 { 3203 hpa: autoscaling.HorizontalPodAutoscaler{ 3204 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 3205 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 3206 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 3207 Name: "some-rc", 3208 Kind: "ReplicationController", 3209 }, 3210 MinReplicas: &minReplicasVal, 3211 MaxReplicas: 10, 3212 Metrics: []autoscaling.MetricSpec{ 3213 { 3214 Type: autoscaling.ResourceMetricSourceType, 3215 Resource: &autoscaling.ResourceMetricSource{ 3216 Name: api.ResourceCPU, 3217 Target: autoscaling.MetricTarget{ 3218 Type: autoscaling.UtilizationMetricType, 3219 AverageUtilization: &targetUtilizationVal, 3220 }, 3221 }, 3222 }, 3223 }, 3224 }, 3225 Status: autoscaling.HorizontalPodAutoscalerStatus{ 3226 CurrentReplicas: 4, 3227 DesiredReplicas: 5, 3228 CurrentMetrics: []autoscaling.MetricStatus{ 3229 { 3230 Type: autoscaling.ResourceMetricSourceType, 3231 Resource: &autoscaling.ResourceMetricStatus{ 3232 Name: api.ResourceCPU, 3233 Current: autoscaling.MetricValueStatus{ 3234 AverageUtilization: ¤tUtilizationVal, 3235 AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI), 3236 }, 3237 }, 3238 }, 3239 }, 3240 }, 3241 }, 3242 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 3243 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50%/80%", "2", int64(10), int64(4), "<unknown>"}}}, 3244 }, 3245 // container resource source type, targetVal (no current) 3246 { 3247 hpa: autoscaling.HorizontalPodAutoscaler{ 3248 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 3249 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 3250 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 3251 Name: "some-rc", 3252 Kind: "ReplicationController", 3253 }, 3254 MinReplicas: &minReplicasVal, 3255 MaxReplicas: 10, 3256 Metrics: []autoscaling.MetricSpec{ 3257 { 3258 Type: autoscaling.ContainerResourceMetricSourceType, 3259 ContainerResource: &autoscaling.ContainerResourceMetricSource{ 3260 Name: api.ResourceCPU, 3261 Container: "application", 3262 Target: autoscaling.MetricTarget{ 3263 Type: autoscaling.AverageValueMetricType, 3264 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), 3265 }, 3266 }, 3267 }, 3268 }, 3269 }, 3270 Status: autoscaling.HorizontalPodAutoscalerStatus{ 3271 CurrentReplicas: 4, 3272 DesiredReplicas: 5, 3273 }, 3274 }, 3275 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 3276 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/100m", "2", int64(10), int64(4), "<unknown>"}}}, 3277 }, 3278 // container resource source type, targetVal 3279 { 3280 hpa: autoscaling.HorizontalPodAutoscaler{ 3281 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 3282 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 3283 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 3284 Name: "some-rc", 3285 Kind: "ReplicationController", 3286 }, 3287 MinReplicas: &minReplicasVal, 3288 MaxReplicas: 10, 3289 Metrics: []autoscaling.MetricSpec{ 3290 { 3291 Type: autoscaling.ContainerResourceMetricSourceType, 3292 ContainerResource: &autoscaling.ContainerResourceMetricSource{ 3293 Name: api.ResourceCPU, 3294 Target: autoscaling.MetricTarget{ 3295 Type: autoscaling.AverageValueMetricType, 3296 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), 3297 }, 3298 }, 3299 }, 3300 }, 3301 }, 3302 Status: autoscaling.HorizontalPodAutoscalerStatus{ 3303 CurrentReplicas: 4, 3304 DesiredReplicas: 5, 3305 CurrentMetrics: []autoscaling.MetricStatus{ 3306 { 3307 Type: autoscaling.ContainerResourceMetricSourceType, 3308 ContainerResource: &autoscaling.ContainerResourceMetricStatus{ 3309 Name: api.ResourceCPU, 3310 Container: "application", 3311 Current: autoscaling.MetricValueStatus{ 3312 AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), 3313 }, 3314 }, 3315 }, 3316 }, 3317 }, 3318 }, 3319 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 3320 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), "<unknown>"}}}, 3321 }, 3322 // container resource source type, targetUtil (no current) 3323 { 3324 hpa: autoscaling.HorizontalPodAutoscaler{ 3325 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 3326 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 3327 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 3328 Name: "some-rc", 3329 Kind: "ReplicationController", 3330 }, 3331 MinReplicas: &minReplicasVal, 3332 MaxReplicas: 10, 3333 Metrics: []autoscaling.MetricSpec{ 3334 { 3335 Type: autoscaling.ContainerResourceMetricSourceType, 3336 ContainerResource: &autoscaling.ContainerResourceMetricSource{ 3337 Name: api.ResourceCPU, 3338 Container: "application", 3339 Target: autoscaling.MetricTarget{ 3340 Type: autoscaling.UtilizationMetricType, 3341 AverageUtilization: &targetUtilizationVal, 3342 }, 3343 }, 3344 }, 3345 }, 3346 }, 3347 Status: autoscaling.HorizontalPodAutoscalerStatus{ 3348 CurrentReplicas: 4, 3349 DesiredReplicas: 5, 3350 }, 3351 }, 3352 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 3353 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/80%", "2", int64(10), int64(4), "<unknown>"}}}, 3354 }, 3355 // container resource source type, targetUtil 3356 { 3357 hpa: autoscaling.HorizontalPodAutoscaler{ 3358 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 3359 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 3360 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 3361 Name: "some-rc", 3362 Kind: "ReplicationController", 3363 }, 3364 MinReplicas: &minReplicasVal, 3365 MaxReplicas: 10, 3366 Metrics: []autoscaling.MetricSpec{ 3367 { 3368 Type: autoscaling.ContainerResourceMetricSourceType, 3369 ContainerResource: &autoscaling.ContainerResourceMetricSource{ 3370 Name: api.ResourceCPU, 3371 Target: autoscaling.MetricTarget{ 3372 Type: autoscaling.UtilizationMetricType, 3373 AverageUtilization: &targetUtilizationVal, 3374 }, 3375 }, 3376 }, 3377 }, 3378 }, 3379 Status: autoscaling.HorizontalPodAutoscalerStatus{ 3380 CurrentReplicas: 4, 3381 DesiredReplicas: 5, 3382 CurrentMetrics: []autoscaling.MetricStatus{ 3383 { 3384 Type: autoscaling.ContainerResourceMetricSourceType, 3385 ContainerResource: &autoscaling.ContainerResourceMetricStatus{ 3386 Name: api.ResourceCPU, 3387 Container: "application", 3388 Current: autoscaling.MetricValueStatus{ 3389 AverageUtilization: ¤tUtilizationVal, 3390 AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI), 3391 }, 3392 }, 3393 }, 3394 }, 3395 }, 3396 }, 3397 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 3398 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50%/80%", "2", int64(10), int64(4), "<unknown>"}}}, 3399 }, 3400 // multiple specs 3401 { 3402 hpa: autoscaling.HorizontalPodAutoscaler{ 3403 ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, 3404 Spec: autoscaling.HorizontalPodAutoscalerSpec{ 3405 ScaleTargetRef: autoscaling.CrossVersionObjectReference{ 3406 Name: "some-rc", 3407 Kind: "ReplicationController", 3408 }, 3409 MinReplicas: &minReplicasVal, 3410 MaxReplicas: 10, 3411 Metrics: []autoscaling.MetricSpec{ 3412 { 3413 Type: autoscaling.PodsMetricSourceType, 3414 Pods: &autoscaling.PodsMetricSource{ 3415 Metric: autoscaling.MetricIdentifier{ 3416 Name: "some-pods-metric", 3417 }, 3418 Target: autoscaling.MetricTarget{ 3419 Type: autoscaling.AverageValueMetricType, 3420 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), 3421 }, 3422 }, 3423 }, 3424 { 3425 Type: autoscaling.ResourceMetricSourceType, 3426 Resource: &autoscaling.ResourceMetricSource{ 3427 Name: api.ResourceCPU, 3428 Target: autoscaling.MetricTarget{ 3429 Type: autoscaling.UtilizationMetricType, 3430 AverageUtilization: &targetUtilizationVal, 3431 }, 3432 }, 3433 }, 3434 { 3435 Type: autoscaling.PodsMetricSourceType, 3436 Pods: &autoscaling.PodsMetricSource{ 3437 Metric: autoscaling.MetricIdentifier{ 3438 Name: "other-pods-metric", 3439 }, 3440 Target: autoscaling.MetricTarget{ 3441 Type: autoscaling.AverageValueMetricType, 3442 AverageValue: resource.NewMilliQuantity(400, resource.DecimalSI), 3443 }, 3444 }, 3445 }, 3446 }, 3447 }, 3448 Status: autoscaling.HorizontalPodAutoscalerStatus{ 3449 CurrentReplicas: 4, 3450 DesiredReplicas: 5, 3451 CurrentMetrics: []autoscaling.MetricStatus{ 3452 { 3453 Type: autoscaling.PodsMetricSourceType, 3454 Pods: &autoscaling.PodsMetricStatus{ 3455 Metric: autoscaling.MetricIdentifier{ 3456 Name: "some-pods-metric", 3457 }, 3458 Current: autoscaling.MetricValueStatus{ 3459 AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), 3460 }, 3461 }, 3462 }, 3463 { 3464 Type: autoscaling.ResourceMetricSourceType, 3465 Resource: &autoscaling.ResourceMetricStatus{ 3466 Name: api.ResourceCPU, 3467 Current: autoscaling.MetricValueStatus{ 3468 AverageUtilization: ¤tUtilizationVal, 3469 AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI), 3470 }, 3471 }, 3472 }, 3473 }, 3474 }, 3475 }, 3476 // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age 3477 expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m, 50%/80% + 1 more...", "2", int64(10), int64(4), "<unknown>"}}}, 3478 }, 3479 } 3480 3481 for i, test := range tests { 3482 rows, err := printHorizontalPodAutoscaler(&test.hpa, printers.GenerateOptions{}) 3483 if err != nil { 3484 t.Fatal(err) 3485 } 3486 for i := range rows { 3487 rows[i].Object.Object = nil 3488 } 3489 if !reflect.DeepEqual(test.expected, rows) { 3490 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 3491 } 3492 } 3493 } 3494 3495 func TestPrintService(t *testing.T) { 3496 singleExternalIP := []string{"80.11.12.10"} 3497 mulExternalIP := []string{"80.11.12.10", "80.11.12.11"} 3498 tests := []struct { 3499 service api.Service 3500 options printers.GenerateOptions 3501 expected []metav1.TableRow 3502 }{ 3503 { 3504 // Test name, cluster ip, port with protocol 3505 service: api.Service{ 3506 ObjectMeta: metav1.ObjectMeta{Name: "test1"}, 3507 Spec: api.ServiceSpec{ 3508 Type: api.ServiceTypeClusterIP, 3509 Ports: []api.ServicePort{ 3510 { 3511 Protocol: "tcp", 3512 Port: 2233, 3513 }, 3514 }, 3515 ClusterIPs: []string{"10.9.8.7"}, 3516 Selector: map[string]string{"foo": "bar"}, // Does NOT get printed. 3517 }, 3518 }, 3519 options: printers.GenerateOptions{}, 3520 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 3521 expected: []metav1.TableRow{{Cells: []interface{}{"test1", "ClusterIP", "10.9.8.7", "<none>", "2233/tcp", "<unknown>"}}}, 3522 }, 3523 { 3524 // Test generate options: Wide includes selectors. 3525 service: api.Service{ 3526 ObjectMeta: metav1.ObjectMeta{Name: "test1"}, 3527 Spec: api.ServiceSpec{ 3528 Type: api.ServiceTypeClusterIP, 3529 Ports: []api.ServicePort{ 3530 { 3531 Protocol: "tcp", 3532 Port: 2233, 3533 }, 3534 }, 3535 ClusterIPs: []string{"10.9.8.7"}, 3536 Selector: map[string]string{"foo": "bar"}, 3537 }, 3538 }, 3539 options: printers.GenerateOptions{Wide: true}, 3540 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age, Selector 3541 expected: []metav1.TableRow{{Cells: []interface{}{"test1", "ClusterIP", "10.9.8.7", "<none>", "2233/tcp", "<unknown>", "foo=bar"}}}, 3542 }, 3543 { 3544 // Test NodePort service 3545 service: api.Service{ 3546 ObjectMeta: metav1.ObjectMeta{Name: "test2"}, 3547 Spec: api.ServiceSpec{ 3548 Type: api.ServiceTypeNodePort, 3549 Ports: []api.ServicePort{ 3550 { 3551 Protocol: "tcp", 3552 Port: 8888, 3553 NodePort: 9999, 3554 }, 3555 }, 3556 ClusterIPs: []string{"10.9.8.7"}, 3557 }, 3558 }, 3559 options: printers.GenerateOptions{}, 3560 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 3561 expected: []metav1.TableRow{{Cells: []interface{}{"test2", "NodePort", "10.9.8.7", "<none>", "8888:9999/tcp", "<unknown>"}}}, 3562 }, 3563 { 3564 // Test LoadBalancer service 3565 service: api.Service{ 3566 ObjectMeta: metav1.ObjectMeta{Name: "test3"}, 3567 Spec: api.ServiceSpec{ 3568 Type: api.ServiceTypeLoadBalancer, 3569 Ports: []api.ServicePort{ 3570 { 3571 Protocol: "tcp", 3572 Port: 8888, 3573 }, 3574 }, 3575 ClusterIPs: []string{"10.9.8.7"}, 3576 }, 3577 }, 3578 options: printers.GenerateOptions{}, 3579 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 3580 expected: []metav1.TableRow{{Cells: []interface{}{"test3", "LoadBalancer", "10.9.8.7", "<pending>", "8888/tcp", "<unknown>"}}}, 3581 }, 3582 { 3583 // Test LoadBalancer service with single ExternalIP and no LoadBalancerStatus 3584 service: api.Service{ 3585 ObjectMeta: metav1.ObjectMeta{Name: "test4"}, 3586 Spec: api.ServiceSpec{ 3587 Type: api.ServiceTypeLoadBalancer, 3588 Ports: []api.ServicePort{ 3589 { 3590 Protocol: "tcp", 3591 Port: 8888, 3592 }, 3593 }, 3594 ClusterIPs: []string{"10.9.8.7"}, 3595 ExternalIPs: singleExternalIP, 3596 }, 3597 }, 3598 options: printers.GenerateOptions{}, 3599 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 3600 expected: []metav1.TableRow{{Cells: []interface{}{"test4", "LoadBalancer", "10.9.8.7", "80.11.12.10", "8888/tcp", "<unknown>"}}}, 3601 }, 3602 { 3603 // Test LoadBalancer service with single ExternalIP 3604 service: api.Service{ 3605 ObjectMeta: metav1.ObjectMeta{Name: "test5"}, 3606 Spec: api.ServiceSpec{ 3607 Type: api.ServiceTypeLoadBalancer, 3608 Ports: []api.ServicePort{ 3609 { 3610 Protocol: "tcp", 3611 Port: 8888, 3612 }, 3613 }, 3614 ClusterIPs: []string{"10.9.8.7"}, 3615 ExternalIPs: singleExternalIP, 3616 }, 3617 Status: api.ServiceStatus{ 3618 LoadBalancer: api.LoadBalancerStatus{ 3619 Ingress: []api.LoadBalancerIngress{ 3620 { 3621 IP: "3.4.5.6", 3622 Hostname: "test.cluster.com", 3623 }, 3624 }, 3625 }, 3626 }, 3627 }, 3628 options: printers.GenerateOptions{}, 3629 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 3630 expected: []metav1.TableRow{{Cells: []interface{}{"test5", "LoadBalancer", "10.9.8.7", "3.4.5.6,80.11.12.10", "8888/tcp", "<unknown>"}}}, 3631 }, 3632 { 3633 // Test LoadBalancer service with mul ExternalIPs 3634 service: api.Service{ 3635 ObjectMeta: metav1.ObjectMeta{Name: "test6"}, 3636 Spec: api.ServiceSpec{ 3637 Type: api.ServiceTypeLoadBalancer, 3638 Ports: []api.ServicePort{ 3639 { 3640 Protocol: "tcp", 3641 Port: 8888, 3642 }, 3643 }, 3644 ClusterIPs: []string{"10.9.8.7"}, 3645 ExternalIPs: mulExternalIP, 3646 }, 3647 Status: api.ServiceStatus{ 3648 LoadBalancer: api.LoadBalancerStatus{ 3649 Ingress: []api.LoadBalancerIngress{ 3650 { 3651 IP: "2.3.4.5", 3652 Hostname: "test.cluster.local", 3653 }, 3654 { 3655 IP: "3.4.5.6", 3656 Hostname: "test.cluster.com", 3657 }, 3658 }, 3659 }, 3660 }, 3661 }, 3662 options: printers.GenerateOptions{}, 3663 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 3664 expected: []metav1.TableRow{{Cells: []interface{}{"test6", "LoadBalancer", "10.9.8.7", "2.3.4.5,3.4.5.6,80.11.12.10,80.11.12.11", "8888/tcp", "<unknown>"}}}, 3665 }, 3666 { 3667 // Test ExternalName service 3668 service: api.Service{ 3669 ObjectMeta: metav1.ObjectMeta{Name: "test7"}, 3670 Spec: api.ServiceSpec{ 3671 Type: api.ServiceTypeExternalName, 3672 ExternalName: "my.database.example.com", 3673 }, 3674 }, 3675 options: printers.GenerateOptions{}, 3676 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 3677 expected: []metav1.TableRow{{Cells: []interface{}{"test7", "ExternalName", "<none>", "my.database.example.com", "<none>", "<unknown>"}}}, 3678 }, 3679 } 3680 3681 for i, test := range tests { 3682 rows, err := printService(&test.service, test.options) 3683 if err != nil { 3684 t.Fatal(err) 3685 } 3686 for i := range rows { 3687 rows[i].Object.Object = nil 3688 } 3689 if !reflect.DeepEqual(test.expected, rows) { 3690 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 3691 } 3692 } 3693 } 3694 3695 func TestPrintServiceList(t *testing.T) { 3696 serviceList := api.ServiceList{ 3697 Items: []api.Service{ 3698 { 3699 ObjectMeta: metav1.ObjectMeta{Name: "service1"}, 3700 Spec: api.ServiceSpec{ 3701 Type: api.ServiceTypeClusterIP, 3702 Ports: []api.ServicePort{ 3703 { 3704 Protocol: "tcp", 3705 Port: 2233, 3706 }, 3707 }, 3708 ClusterIPs: []string{"10.9.8.7"}, 3709 }, 3710 }, 3711 { 3712 ObjectMeta: metav1.ObjectMeta{Name: "service2"}, 3713 Spec: api.ServiceSpec{ 3714 Type: api.ServiceTypeNodePort, 3715 Ports: []api.ServicePort{ 3716 { 3717 Protocol: "udp", 3718 Port: 5566, 3719 }, 3720 }, 3721 ClusterIPs: []string{"1.2.3.4"}, 3722 }, 3723 }, 3724 }, 3725 } 3726 3727 // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 3728 expectedRows := []metav1.TableRow{ 3729 {Cells: []interface{}{"service1", "ClusterIP", "10.9.8.7", "<none>", "2233/tcp", "<unknown>"}}, 3730 {Cells: []interface{}{"service2", "NodePort", "1.2.3.4", "<none>", "5566/udp", "<unknown>"}}, 3731 } 3732 3733 rows, err := printServiceList(&serviceList, printers.GenerateOptions{}) 3734 if err != nil { 3735 t.Fatalf("Error printing service list: %#v", err) 3736 } 3737 for i := range rows { 3738 rows[i].Object.Object = nil 3739 } 3740 if !reflect.DeepEqual(expectedRows, rows) { 3741 t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) 3742 } 3743 } 3744 3745 func TestPrintPodDisruptionBudget(t *testing.T) { 3746 minAvailable := intstr.FromInt32(22) 3747 maxUnavailable := intstr.FromInt32(11) 3748 tests := []struct { 3749 pdb policy.PodDisruptionBudget 3750 expected []metav1.TableRow 3751 }{ 3752 // Min Available set, no Max Available. 3753 { 3754 pdb: policy.PodDisruptionBudget{ 3755 ObjectMeta: metav1.ObjectMeta{ 3756 Namespace: "ns1", 3757 Name: "pdb1", 3758 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 3759 }, 3760 Spec: policy.PodDisruptionBudgetSpec{ 3761 MinAvailable: &minAvailable, 3762 }, 3763 Status: policy.PodDisruptionBudgetStatus{ 3764 DisruptionsAllowed: 5, 3765 }, 3766 }, 3767 // Columns: Name, Min Available, Max Available, Allowed Disruptions, Age 3768 expected: []metav1.TableRow{{Cells: []interface{}{"pdb1", "22", "N/A", int64(5), "0s"}}}, 3769 }, 3770 // Max Available set, no Min Available. 3771 { 3772 pdb: policy.PodDisruptionBudget{ 3773 ObjectMeta: metav1.ObjectMeta{ 3774 Namespace: "ns2", 3775 Name: "pdb2", 3776 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 3777 }, 3778 Spec: policy.PodDisruptionBudgetSpec{ 3779 MaxUnavailable: &maxUnavailable, 3780 }, 3781 Status: policy.PodDisruptionBudgetStatus{ 3782 DisruptionsAllowed: 5, 3783 }, 3784 }, 3785 // Columns: Name, Min Available, Max Available, Allowed Disruptions, Age 3786 expected: []metav1.TableRow{{Cells: []interface{}{"pdb2", "N/A", "11", int64(5), "0s"}}}, 3787 }} 3788 3789 for i, test := range tests { 3790 rows, err := printPodDisruptionBudget(&test.pdb, printers.GenerateOptions{}) 3791 if err != nil { 3792 t.Fatal(err) 3793 } 3794 for i := range rows { 3795 rows[i].Object.Object = nil 3796 } 3797 if !reflect.DeepEqual(test.expected, rows) { 3798 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 3799 } 3800 } 3801 } 3802 3803 func TestPrintPodDisruptionBudgetList(t *testing.T) { 3804 minAvailable := intstr.FromInt32(22) 3805 maxUnavailable := intstr.FromInt32(11) 3806 3807 pdbList := policy.PodDisruptionBudgetList{ 3808 Items: []policy.PodDisruptionBudget{ 3809 { 3810 ObjectMeta: metav1.ObjectMeta{ 3811 Namespace: "ns1", 3812 Name: "pdb1", 3813 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 3814 }, 3815 Spec: policy.PodDisruptionBudgetSpec{ 3816 MaxUnavailable: &maxUnavailable, 3817 }, 3818 Status: policy.PodDisruptionBudgetStatus{ 3819 DisruptionsAllowed: 5, 3820 }, 3821 }, 3822 { 3823 ObjectMeta: metav1.ObjectMeta{ 3824 Namespace: "ns2", 3825 Name: "pdb2", 3826 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 3827 }, 3828 Spec: policy.PodDisruptionBudgetSpec{ 3829 MinAvailable: &minAvailable, 3830 }, 3831 Status: policy.PodDisruptionBudgetStatus{ 3832 DisruptionsAllowed: 3, 3833 }, 3834 }, 3835 }, 3836 } 3837 3838 // Columns: Name, Min Available, Max Available, Allowed Disruptions, Age 3839 expectedRows := []metav1.TableRow{ 3840 {Cells: []interface{}{"pdb1", "N/A", "11", int64(5), "0s"}}, 3841 {Cells: []interface{}{"pdb2", "22", "N/A", int64(3), "0s"}}, 3842 } 3843 3844 rows, err := printPodDisruptionBudgetList(&pdbList, printers.GenerateOptions{}) 3845 if err != nil { 3846 t.Fatalf("Error printing pod template list: %#v", err) 3847 } 3848 for i := range rows { 3849 rows[i].Object.Object = nil 3850 } 3851 if !reflect.DeepEqual(expectedRows, rows) { 3852 t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) 3853 } 3854 } 3855 3856 func TestPrintControllerRevision(t *testing.T) { 3857 tests := []struct { 3858 history apps.ControllerRevision 3859 expected []metav1.TableRow 3860 }{ 3861 { 3862 history: apps.ControllerRevision{ 3863 ObjectMeta: metav1.ObjectMeta{ 3864 Name: "test1", 3865 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 3866 OwnerReferences: []metav1.OwnerReference{ 3867 { 3868 Controller: boolP(true), 3869 APIVersion: "apps/v1", 3870 Kind: "DaemonSet", 3871 Name: "foo", 3872 }, 3873 }, 3874 }, 3875 Revision: 1, 3876 }, 3877 expected: []metav1.TableRow{{Cells: []interface{}{"test1", "daemonset.apps/foo", int64(1), "0s"}}}, 3878 }, 3879 { 3880 history: apps.ControllerRevision{ 3881 ObjectMeta: metav1.ObjectMeta{ 3882 Name: "test2", 3883 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 3884 OwnerReferences: []metav1.OwnerReference{ 3885 { 3886 Controller: boolP(false), 3887 Kind: "ABC", 3888 Name: "foo", 3889 }, 3890 }, 3891 }, 3892 Revision: 2, 3893 }, 3894 expected: []metav1.TableRow{{Cells: []interface{}{"test2", "<none>", int64(2), "0s"}}}, 3895 }, 3896 { 3897 history: apps.ControllerRevision{ 3898 ObjectMeta: metav1.ObjectMeta{ 3899 Name: "test3", 3900 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 3901 OwnerReferences: []metav1.OwnerReference{}, 3902 }, 3903 Revision: 3, 3904 }, 3905 expected: []metav1.TableRow{{Cells: []interface{}{"test3", "<none>", int64(3), "0s"}}}, 3906 }, 3907 { 3908 history: apps.ControllerRevision{ 3909 ObjectMeta: metav1.ObjectMeta{ 3910 Name: "test4", 3911 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 3912 OwnerReferences: nil, 3913 }, 3914 Revision: 4, 3915 }, 3916 expected: []metav1.TableRow{{Cells: []interface{}{"test4", "<none>", int64(4), "0s"}}}, 3917 }, 3918 } 3919 3920 for i, test := range tests { 3921 rows, err := printControllerRevision(&test.history, printers.GenerateOptions{}) 3922 if err != nil { 3923 t.Fatal(err) 3924 } 3925 for i := range rows { 3926 rows[i].Object.Object = nil 3927 } 3928 if !reflect.DeepEqual(test.expected, rows) { 3929 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 3930 } 3931 } 3932 } 3933 3934 func boolP(b bool) *bool { 3935 return &b 3936 } 3937 3938 func TestPrintConfigMap(t *testing.T) { 3939 tests := []struct { 3940 configMap api.ConfigMap 3941 expected []metav1.TableRow 3942 }{ 3943 // Basic config map with no data. 3944 { 3945 configMap: api.ConfigMap{ 3946 ObjectMeta: metav1.ObjectMeta{ 3947 Name: "configmap1", 3948 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 3949 }, 3950 }, 3951 // Columns: Name, Data, Age 3952 expected: []metav1.TableRow{{Cells: []interface{}{"configmap1", int64(0), "0s"}}}, 3953 }, 3954 // Basic config map with one data entry 3955 { 3956 configMap: api.ConfigMap{ 3957 ObjectMeta: metav1.ObjectMeta{ 3958 Name: "configmap2", 3959 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 3960 }, 3961 Data: map[string]string{ 3962 "foo": "bar", 3963 }, 3964 }, 3965 // Columns: Name, (Num) Data, Age 3966 expected: []metav1.TableRow{{Cells: []interface{}{"configmap2", int64(1), "0s"}}}, 3967 }, 3968 // Basic config map with one data and one binary data entry. 3969 { 3970 configMap: api.ConfigMap{ 3971 ObjectMeta: metav1.ObjectMeta{ 3972 Name: "configmap3", 3973 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 3974 }, 3975 Data: map[string]string{ 3976 "foo": "bar", 3977 }, 3978 BinaryData: map[string][]byte{ 3979 "bin": []byte("binary data"), 3980 }, 3981 }, 3982 // Columns: Name, (Num) Data, Age 3983 expected: []metav1.TableRow{{Cells: []interface{}{"configmap3", int64(2), "0s"}}}, 3984 }, 3985 } 3986 3987 for i, test := range tests { 3988 rows, err := printConfigMap(&test.configMap, printers.GenerateOptions{}) 3989 if err != nil { 3990 t.Fatal(err) 3991 } 3992 for i := range rows { 3993 rows[i].Object.Object = nil 3994 } 3995 if !reflect.DeepEqual(test.expected, rows) { 3996 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 3997 } 3998 } 3999 } 4000 4001 func TestPrintNetworkPolicy(t *testing.T) { 4002 tests := []struct { 4003 policy networking.NetworkPolicy 4004 expected []metav1.TableRow 4005 }{ 4006 // Basic network policy with empty spec. 4007 { 4008 policy: networking.NetworkPolicy{ 4009 ObjectMeta: metav1.ObjectMeta{ 4010 Name: "policy1", 4011 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4012 }, 4013 Spec: networking.NetworkPolicySpec{}, 4014 }, 4015 // Columns: Name, Pod-Selector, Age 4016 expected: []metav1.TableRow{{Cells: []interface{}{"policy1", "<none>", "0s"}}}, 4017 }, 4018 // Basic network policy with pod selector. 4019 { 4020 policy: networking.NetworkPolicy{ 4021 ObjectMeta: metav1.ObjectMeta{ 4022 Name: "policy2", 4023 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4024 }, 4025 Spec: networking.NetworkPolicySpec{ 4026 PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 4027 }, 4028 }, 4029 // Columns: Name, Pod-Selector, Age 4030 expected: []metav1.TableRow{{Cells: []interface{}{"policy2", "foo=bar", "0s"}}}, 4031 }, 4032 } 4033 4034 for i, test := range tests { 4035 rows, err := printNetworkPolicy(&test.policy, printers.GenerateOptions{}) 4036 if err != nil { 4037 t.Fatal(err) 4038 } 4039 for i := range rows { 4040 rows[i].Object.Object = nil 4041 } 4042 if !reflect.DeepEqual(test.expected, rows) { 4043 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 4044 } 4045 } 4046 } 4047 4048 func TestPrintRoleBinding(t *testing.T) { 4049 tests := []struct { 4050 binding rbac.RoleBinding 4051 options printers.GenerateOptions 4052 expected []metav1.TableRow 4053 }{ 4054 // Basic role binding 4055 { 4056 binding: rbac.RoleBinding{ 4057 ObjectMeta: metav1.ObjectMeta{ 4058 Name: "binding1", 4059 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4060 }, 4061 Subjects: []rbac.Subject{ 4062 { 4063 Kind: "User", 4064 Name: "system:kube-controller-manager", 4065 }, 4066 }, 4067 RoleRef: rbac.RoleRef{ 4068 Kind: "Role", 4069 Name: "extension-apiserver-authentication-reader", 4070 }, 4071 }, 4072 options: printers.GenerateOptions{}, 4073 // Columns: Name, Age 4074 expected: []metav1.TableRow{{Cells: []interface{}{"binding1", "Role/extension-apiserver-authentication-reader", "0s"}}}, 4075 }, 4076 // Generate options=Wide; print subject and roles. 4077 { 4078 binding: rbac.RoleBinding{ 4079 ObjectMeta: metav1.ObjectMeta{ 4080 Name: "binding2", 4081 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4082 }, 4083 Subjects: []rbac.Subject{ 4084 { 4085 Kind: "User", 4086 Name: "user-name", 4087 }, 4088 { 4089 Kind: "Group", 4090 Name: "group-name", 4091 }, 4092 { 4093 Kind: "ServiceAccount", 4094 Name: "service-account-name", 4095 Namespace: "service-account-namespace", 4096 }, 4097 }, 4098 RoleRef: rbac.RoleRef{ 4099 Kind: "Role", 4100 Name: "role-name", 4101 }, 4102 }, 4103 options: printers.GenerateOptions{Wide: true}, 4104 // Columns: Name, Age, Role, Users, Groups, ServiceAccounts 4105 expected: []metav1.TableRow{{Cells: []interface{}{"binding2", "Role/role-name", "0s", "user-name", "group-name", "service-account-namespace/service-account-name"}}}, 4106 }, 4107 } 4108 4109 for i, test := range tests { 4110 rows, err := printRoleBinding(&test.binding, test.options) 4111 if err != nil { 4112 t.Fatal(err) 4113 } 4114 for i := range rows { 4115 rows[i].Object.Object = nil 4116 } 4117 if !reflect.DeepEqual(test.expected, rows) { 4118 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 4119 } 4120 } 4121 } 4122 4123 func TestPrintClusterRoleBinding(t *testing.T) { 4124 tests := []struct { 4125 binding rbac.ClusterRoleBinding 4126 options printers.GenerateOptions 4127 expected []metav1.TableRow 4128 }{ 4129 // Basic cluster role binding 4130 { 4131 binding: rbac.ClusterRoleBinding{ 4132 ObjectMeta: metav1.ObjectMeta{ 4133 Name: "binding1", 4134 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4135 }, 4136 Subjects: []rbac.Subject{ 4137 { 4138 Kind: "User", 4139 Name: "system:kube-controller-manager", 4140 }, 4141 }, 4142 RoleRef: rbac.RoleRef{ 4143 Kind: "Role", 4144 Name: "extension-apiserver-authentication-reader", 4145 }, 4146 }, 4147 options: printers.GenerateOptions{}, 4148 // Columns: Name, Age 4149 expected: []metav1.TableRow{{Cells: []interface{}{"binding1", "Role/extension-apiserver-authentication-reader", "0s"}}}, 4150 }, 4151 // Generate options=Wide; print subject and roles. 4152 { 4153 binding: rbac.ClusterRoleBinding{ 4154 ObjectMeta: metav1.ObjectMeta{ 4155 Name: "binding2", 4156 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4157 }, 4158 Subjects: []rbac.Subject{ 4159 { 4160 Kind: "User", 4161 Name: "user-name", 4162 }, 4163 { 4164 Kind: "Group", 4165 Name: "group-name", 4166 }, 4167 { 4168 Kind: "ServiceAccount", 4169 Name: "service-account-name", 4170 Namespace: "service-account-namespace", 4171 }, 4172 }, 4173 RoleRef: rbac.RoleRef{ 4174 Kind: "Role", 4175 Name: "role-name", 4176 }, 4177 }, 4178 options: printers.GenerateOptions{Wide: true}, 4179 // Columns: Name, Age, Role, Users, Groups, ServiceAccounts 4180 expected: []metav1.TableRow{{Cells: []interface{}{"binding2", "Role/role-name", "0s", "user-name", "group-name", "service-account-namespace/service-account-name"}}}, 4181 }, 4182 } 4183 4184 for i, test := range tests { 4185 rows, err := printClusterRoleBinding(&test.binding, test.options) 4186 if err != nil { 4187 t.Fatal(err) 4188 } 4189 for i := range rows { 4190 rows[i].Object.Object = nil 4191 } 4192 if !reflect.DeepEqual(test.expected, rows) { 4193 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 4194 } 4195 } 4196 } 4197 func TestPrintCertificateSigningRequest(t *testing.T) { 4198 tests := []struct { 4199 csr certificates.CertificateSigningRequest 4200 expected []metav1.TableRow 4201 }{ 4202 // Basic CSR with no spec or status; defaults to status: Pending. 4203 { 4204 csr: certificates.CertificateSigningRequest{ 4205 ObjectMeta: metav1.ObjectMeta{ 4206 Name: "csr1", 4207 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4208 }, 4209 Spec: certificates.CertificateSigningRequestSpec{}, 4210 Status: certificates.CertificateSigningRequestStatus{}, 4211 }, 4212 // Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition 4213 expected: []metav1.TableRow{{Cells: []interface{}{"csr1", "0s", "<none>", "", "<none>", "Pending"}}}, 4214 }, 4215 // Basic CSR with Spec and Status=Approved. 4216 { 4217 csr: certificates.CertificateSigningRequest{ 4218 ObjectMeta: metav1.ObjectMeta{ 4219 Name: "csr2", 4220 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4221 }, 4222 Spec: certificates.CertificateSigningRequestSpec{ 4223 Username: "CSR Requestor", 4224 }, 4225 Status: certificates.CertificateSigningRequestStatus{ 4226 Conditions: []certificates.CertificateSigningRequestCondition{ 4227 { 4228 Type: certificates.CertificateApproved, 4229 }, 4230 }, 4231 }, 4232 }, 4233 // Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition 4234 expected: []metav1.TableRow{{Cells: []interface{}{"csr2", "0s", "<none>", "CSR Requestor", "<none>", "Approved"}}}, 4235 }, 4236 // Basic CSR with Spec and SignerName set 4237 { 4238 csr: certificates.CertificateSigningRequest{ 4239 ObjectMeta: metav1.ObjectMeta{ 4240 Name: "csr2", 4241 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4242 }, 4243 Spec: certificates.CertificateSigningRequestSpec{ 4244 Username: "CSR Requestor", 4245 SignerName: "example.com/test-signer", 4246 }, 4247 Status: certificates.CertificateSigningRequestStatus{ 4248 Conditions: []certificates.CertificateSigningRequestCondition{ 4249 { 4250 Type: certificates.CertificateApproved, 4251 }, 4252 }, 4253 }, 4254 }, 4255 // Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition 4256 expected: []metav1.TableRow{{Cells: []interface{}{"csr2", "0s", "example.com/test-signer", "CSR Requestor", "<none>", "Approved"}}}, 4257 }, 4258 // Basic CSR with Spec, SignerName and ExpirationSeconds set 4259 { 4260 csr: certificates.CertificateSigningRequest{ 4261 ObjectMeta: metav1.ObjectMeta{ 4262 Name: "csr2", 4263 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4264 }, 4265 Spec: certificates.CertificateSigningRequestSpec{ 4266 Username: "CSR Requestor", 4267 SignerName: "example.com/test-signer", 4268 ExpirationSeconds: csr.DurationToExpirationSeconds(7*24*time.Hour + time.Hour), // a little bit more than a week 4269 }, 4270 Status: certificates.CertificateSigningRequestStatus{ 4271 Conditions: []certificates.CertificateSigningRequestCondition{ 4272 { 4273 Type: certificates.CertificateApproved, 4274 }, 4275 }, 4276 }, 4277 }, 4278 // Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition 4279 expected: []metav1.TableRow{{Cells: []interface{}{"csr2", "0s", "example.com/test-signer", "CSR Requestor", "7d1h", "Approved"}}}, 4280 }, 4281 // Basic CSR with Spec and Status=Approved; certificate issued. 4282 { 4283 csr: certificates.CertificateSigningRequest{ 4284 ObjectMeta: metav1.ObjectMeta{ 4285 Name: "csr2", 4286 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4287 }, 4288 Spec: certificates.CertificateSigningRequestSpec{ 4289 Username: "CSR Requestor", 4290 }, 4291 Status: certificates.CertificateSigningRequestStatus{ 4292 Conditions: []certificates.CertificateSigningRequestCondition{ 4293 { 4294 Type: certificates.CertificateApproved, 4295 }, 4296 }, 4297 Certificate: []byte("cert data"), 4298 }, 4299 }, 4300 // Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition 4301 expected: []metav1.TableRow{{Cells: []interface{}{"csr2", "0s", "<none>", "CSR Requestor", "<none>", "Approved,Issued"}}}, 4302 }, 4303 // Basic CSR with Spec and Status=Denied. 4304 { 4305 csr: certificates.CertificateSigningRequest{ 4306 ObjectMeta: metav1.ObjectMeta{ 4307 Name: "csr3", 4308 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4309 }, 4310 Spec: certificates.CertificateSigningRequestSpec{ 4311 Username: "CSR Requestor", 4312 }, 4313 Status: certificates.CertificateSigningRequestStatus{ 4314 Conditions: []certificates.CertificateSigningRequestCondition{ 4315 { 4316 Type: certificates.CertificateDenied, 4317 }, 4318 }, 4319 }, 4320 }, 4321 // Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition 4322 expected: []metav1.TableRow{{Cells: []interface{}{"csr3", "0s", "<none>", "CSR Requestor", "<none>", "Denied"}}}, 4323 }, 4324 } 4325 4326 for i, test := range tests { 4327 rows, err := printCertificateSigningRequest(&test.csr, printers.GenerateOptions{}) 4328 if err != nil { 4329 t.Fatal(err) 4330 } 4331 for i := range rows { 4332 rows[i].Object.Object = nil 4333 } 4334 if !reflect.DeepEqual(test.expected, rows) { 4335 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 4336 } 4337 } 4338 } 4339 4340 func TestPrintReplicationController(t *testing.T) { 4341 tests := []struct { 4342 rc api.ReplicationController 4343 options printers.GenerateOptions 4344 expected []metav1.TableRow 4345 }{ 4346 // Basic print replication controller without replicas or status. 4347 { 4348 rc: api.ReplicationController{ 4349 ObjectMeta: metav1.ObjectMeta{ 4350 Name: "rc1", 4351 Namespace: "test-namespace", 4352 }, 4353 Spec: api.ReplicationControllerSpec{ 4354 Selector: map[string]string{"a": "b"}, 4355 Template: &api.PodTemplateSpec{ 4356 ObjectMeta: metav1.ObjectMeta{ 4357 Labels: map[string]string{"a": "b"}, 4358 }, 4359 Spec: api.PodSpec{ 4360 Containers: []api.Container{ 4361 { 4362 Name: "test", 4363 Image: "test_image", 4364 ImagePullPolicy: api.PullIfNotPresent, 4365 TerminationMessagePolicy: api.TerminationMessageReadFile, 4366 }, 4367 }, 4368 RestartPolicy: api.RestartPolicyAlways, 4369 DNSPolicy: api.DNSClusterFirst, 4370 }, 4371 }, 4372 }, 4373 }, 4374 options: printers.GenerateOptions{}, 4375 // Columns: Name, Desired, Current, Ready, Age 4376 expected: []metav1.TableRow{{Cells: []interface{}{"rc1", int64(0), int64(0), int64(0), "<unknown>"}}}, 4377 }, 4378 // Basic print replication controller with replicas; does not print containers or labels 4379 { 4380 rc: api.ReplicationController{ 4381 ObjectMeta: metav1.ObjectMeta{ 4382 Name: "rc1", 4383 Namespace: "test-namespace", 4384 }, 4385 Spec: api.ReplicationControllerSpec{ 4386 Replicas: 5, 4387 Selector: map[string]string{"a": "b"}, 4388 Template: &api.PodTemplateSpec{ 4389 ObjectMeta: metav1.ObjectMeta{ 4390 Labels: map[string]string{"a": "b"}, 4391 }, 4392 Spec: api.PodSpec{ 4393 Containers: []api.Container{ 4394 { 4395 Name: "test", 4396 Image: "test_image", 4397 ImagePullPolicy: api.PullIfNotPresent, 4398 TerminationMessagePolicy: api.TerminationMessageReadFile, 4399 }, 4400 }, 4401 RestartPolicy: api.RestartPolicyAlways, 4402 DNSPolicy: api.DNSClusterFirst, 4403 }, 4404 }, 4405 }, 4406 Status: api.ReplicationControllerStatus{ 4407 Replicas: 3, 4408 ReadyReplicas: 1, 4409 }, 4410 }, 4411 options: printers.GenerateOptions{}, 4412 // Columns: Name, Desired, Current, Ready, Age 4413 expected: []metav1.TableRow{{Cells: []interface{}{"rc1", int64(5), int64(3), int64(1), "<unknown>"}}}, 4414 }, 4415 // Generate options: Wide; print containers and labels. 4416 { 4417 rc: api.ReplicationController{ 4418 ObjectMeta: metav1.ObjectMeta{ 4419 Name: "rc1", 4420 }, 4421 Spec: api.ReplicationControllerSpec{ 4422 Replicas: 5, 4423 Selector: map[string]string{"a": "b"}, 4424 Template: &api.PodTemplateSpec{ 4425 ObjectMeta: metav1.ObjectMeta{ 4426 Labels: map[string]string{"a": "b"}, 4427 }, 4428 Spec: api.PodSpec{ 4429 Containers: []api.Container{ 4430 { 4431 Name: "test", 4432 Image: "test_image", 4433 ImagePullPolicy: api.PullIfNotPresent, 4434 TerminationMessagePolicy: api.TerminationMessageReadFile, 4435 }, 4436 }, 4437 RestartPolicy: api.RestartPolicyAlways, 4438 DNSPolicy: api.DNSClusterFirst, 4439 }, 4440 }, 4441 }, 4442 Status: api.ReplicationControllerStatus{ 4443 Replicas: 3, 4444 ReadyReplicas: 1, 4445 }, 4446 }, 4447 options: printers.GenerateOptions{Wide: true}, 4448 // Columns: Name, Desired, Current, Ready, Age, Containers, Images, Selector 4449 expected: []metav1.TableRow{{Cells: []interface{}{"rc1", int64(5), int64(3), int64(1), "<unknown>", "test", "test_image", "a=b"}}}, 4450 }, 4451 } 4452 4453 for i, test := range tests { 4454 rows, err := printReplicationController(&test.rc, test.options) 4455 if err != nil { 4456 t.Fatal(err) 4457 } 4458 for i := range rows { 4459 rows[i].Object.Object = nil 4460 } 4461 if !reflect.DeepEqual(test.expected, rows) { 4462 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 4463 } 4464 } 4465 } 4466 4467 func TestPrintReplicaSet(t *testing.T) { 4468 tests := []struct { 4469 replicaSet apps.ReplicaSet 4470 options printers.GenerateOptions 4471 expected []metav1.TableRow 4472 }{ 4473 // Generate options empty 4474 { 4475 replicaSet: apps.ReplicaSet{ 4476 ObjectMeta: metav1.ObjectMeta{ 4477 Name: "test1", 4478 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4479 }, 4480 Spec: apps.ReplicaSetSpec{ 4481 Replicas: 5, 4482 Template: api.PodTemplateSpec{ 4483 Spec: api.PodSpec{ 4484 Containers: []api.Container{ 4485 { 4486 Name: "fake-container1", 4487 Image: "fake-image1", 4488 }, 4489 { 4490 Name: "fake-container2", 4491 Image: "fake-image2", 4492 }, 4493 }, 4494 }, 4495 }, 4496 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 4497 }, 4498 Status: apps.ReplicaSetStatus{ 4499 Replicas: 5, 4500 ReadyReplicas: 2, 4501 }, 4502 }, 4503 options: printers.GenerateOptions{}, 4504 // Columns: Name, Desired, Current, Ready, Age 4505 expected: []metav1.TableRow{{Cells: []interface{}{"test1", int64(5), int64(5), int64(2), "0s"}}}, 4506 }, 4507 // Generate options "Wide" 4508 { 4509 replicaSet: apps.ReplicaSet{ 4510 ObjectMeta: metav1.ObjectMeta{ 4511 Name: "test1", 4512 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4513 }, 4514 Spec: apps.ReplicaSetSpec{ 4515 Replicas: 5, 4516 Template: api.PodTemplateSpec{ 4517 Spec: api.PodSpec{ 4518 Containers: []api.Container{ 4519 { 4520 Name: "fake-container1", 4521 Image: "fake-image1", 4522 }, 4523 { 4524 Name: "fake-container2", 4525 Image: "fake-image2", 4526 }, 4527 }, 4528 }, 4529 }, 4530 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 4531 }, 4532 Status: apps.ReplicaSetStatus{ 4533 Replicas: 5, 4534 ReadyReplicas: 2, 4535 }, 4536 }, 4537 options: printers.GenerateOptions{Wide: true}, 4538 // Columns: Name, Desired, Current, Ready, Age, Containers, Images, Selector 4539 expected: []metav1.TableRow{{Cells: []interface{}{"test1", int64(5), int64(5), int64(2), "0s", "fake-container1,fake-container2", "fake-image1,fake-image2", "foo=bar"}}}, 4540 }, 4541 } 4542 4543 for i, test := range tests { 4544 rows, err := printReplicaSet(&test.replicaSet, test.options) 4545 if err != nil { 4546 t.Fatal(err) 4547 } 4548 for i := range rows { 4549 rows[i].Object.Object = nil 4550 } 4551 if !reflect.DeepEqual(test.expected, rows) { 4552 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 4553 } 4554 } 4555 } 4556 4557 func TestPrintReplicaSetList(t *testing.T) { 4558 4559 replicaSetList := apps.ReplicaSetList{ 4560 Items: []apps.ReplicaSet{ 4561 { 4562 ObjectMeta: metav1.ObjectMeta{ 4563 Name: "replicaset1", 4564 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4565 }, 4566 Spec: apps.ReplicaSetSpec{ 4567 Replicas: 5, 4568 Template: api.PodTemplateSpec{ 4569 Spec: api.PodSpec{ 4570 Containers: []api.Container{ 4571 { 4572 Name: "fake-container1", 4573 Image: "fake-image1", 4574 }, 4575 { 4576 Name: "fake-container2", 4577 Image: "fake-image2", 4578 }, 4579 }, 4580 }, 4581 }, 4582 }, 4583 Status: apps.ReplicaSetStatus{ 4584 Replicas: 5, 4585 ReadyReplicas: 2, 4586 }, 4587 }, 4588 { 4589 ObjectMeta: metav1.ObjectMeta{ 4590 Name: "replicaset2", 4591 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4592 }, 4593 Spec: apps.ReplicaSetSpec{ 4594 Replicas: 4, 4595 Template: api.PodTemplateSpec{}, 4596 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 4597 }, 4598 Status: apps.ReplicaSetStatus{ 4599 Replicas: 3, 4600 ReadyReplicas: 1, 4601 }, 4602 }, 4603 }, 4604 } 4605 4606 // Columns: Name, Desired, Current, Ready, Age 4607 expectedRows := []metav1.TableRow{ 4608 {Cells: []interface{}{"replicaset1", int64(5), int64(5), int64(2), "0s"}}, 4609 {Cells: []interface{}{"replicaset2", int64(4), int64(3), int64(1), "0s"}}, 4610 } 4611 4612 rows, err := printReplicaSetList(&replicaSetList, printers.GenerateOptions{}) 4613 if err != nil { 4614 t.Fatalf("Error printing replica set list: %#v", err) 4615 } 4616 for i := range rows { 4617 rows[i].Object.Object = nil 4618 } 4619 if !reflect.DeepEqual(expectedRows, rows) { 4620 t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) 4621 } 4622 } 4623 4624 func TestPrintStatefulSet(t *testing.T) { 4625 tests := []struct { 4626 statefulSet apps.StatefulSet 4627 options printers.GenerateOptions 4628 expected []metav1.TableRow 4629 }{ 4630 // Basic stateful set; no generate options. 4631 { 4632 statefulSet: apps.StatefulSet{ 4633 ObjectMeta: metav1.ObjectMeta{ 4634 Name: "test1", 4635 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4636 }, 4637 Spec: apps.StatefulSetSpec{ 4638 Replicas: 5, 4639 Template: api.PodTemplateSpec{ 4640 Spec: api.PodSpec{ 4641 Containers: []api.Container{ 4642 { 4643 Name: "fake-container1", 4644 Image: "fake-image1", 4645 }, 4646 { 4647 Name: "fake-container2", 4648 Image: "fake-image2", 4649 }, 4650 }, 4651 }, 4652 }, 4653 }, 4654 Status: apps.StatefulSetStatus{ 4655 Replicas: 5, 4656 ReadyReplicas: 2, 4657 }, 4658 }, 4659 options: printers.GenerateOptions{}, 4660 // Columns: Name, Ready, Age 4661 expected: []metav1.TableRow{{Cells: []interface{}{"test1", "2/5", "0s"}}}, 4662 }, 4663 // Generate options "Wide"; includes containers and images. 4664 { 4665 statefulSet: apps.StatefulSet{ 4666 ObjectMeta: metav1.ObjectMeta{ 4667 Name: "test1", 4668 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 4669 }, 4670 Spec: apps.StatefulSetSpec{ 4671 Replicas: 5, 4672 Template: api.PodTemplateSpec{ 4673 Spec: api.PodSpec{ 4674 Containers: []api.Container{ 4675 { 4676 Name: "fake-container1", 4677 Image: "fake-image1", 4678 }, 4679 { 4680 Name: "fake-container2", 4681 Image: "fake-image2", 4682 }, 4683 }, 4684 }, 4685 }, 4686 }, 4687 Status: apps.StatefulSetStatus{ 4688 Replicas: 5, 4689 ReadyReplicas: 2, 4690 }, 4691 }, 4692 options: printers.GenerateOptions{Wide: true}, 4693 // Columns: Name, Ready, Age, Containers, Images 4694 expected: []metav1.TableRow{{Cells: []interface{}{"test1", "2/5", "0s", "fake-container1,fake-container2", "fake-image1,fake-image2"}}}, 4695 }, 4696 } 4697 4698 for i, test := range tests { 4699 rows, err := printStatefulSet(&test.statefulSet, test.options) 4700 if err != nil { 4701 t.Fatal(err) 4702 } 4703 for i := range rows { 4704 rows[i].Object.Object = nil 4705 } 4706 if !reflect.DeepEqual(test.expected, rows) { 4707 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 4708 } 4709 } 4710 } 4711 4712 func TestPrintPersistentVolume(t *testing.T) { 4713 myScn := "my-scn" 4714 myVacn := "my-vacn" 4715 4716 claimRef := api.ObjectReference{ 4717 Name: "test", 4718 Namespace: "default", 4719 } 4720 tests := []struct { 4721 pv api.PersistentVolume 4722 expected []metav1.TableRow 4723 }{ 4724 { 4725 // Test bound 4726 pv: api.PersistentVolume{ 4727 ObjectMeta: metav1.ObjectMeta{ 4728 Name: "test1", 4729 }, 4730 Spec: api.PersistentVolumeSpec{ 4731 ClaimRef: &claimRef, 4732 AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany}, 4733 Capacity: map[api.ResourceName]resource.Quantity{ 4734 api.ResourceStorage: resource.MustParse("4Gi"), 4735 }, 4736 }, 4737 Status: api.PersistentVolumeStatus{ 4738 Phase: api.VolumeBound, 4739 }, 4740 }, 4741 expected: []metav1.TableRow{{Cells: []interface{}{"test1", "4Gi", "ROX", "", "Bound", "default/test", "", "<unset>", "", "<unknown>", "<unset>"}}}, 4742 }, 4743 { 4744 // Test failed 4745 pv: api.PersistentVolume{ 4746 ObjectMeta: metav1.ObjectMeta{ 4747 Name: "test2", 4748 }, 4749 Spec: api.PersistentVolumeSpec{ 4750 ClaimRef: &claimRef, 4751 AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany}, 4752 Capacity: map[api.ResourceName]resource.Quantity{ 4753 api.ResourceStorage: resource.MustParse("4Gi"), 4754 }, 4755 }, 4756 Status: api.PersistentVolumeStatus{ 4757 Phase: api.VolumeFailed, 4758 }, 4759 }, 4760 expected: []metav1.TableRow{{Cells: []interface{}{"test2", "4Gi", "ROX", "", "Failed", "default/test", "", "<unset>", "", "<unknown>", "<unset>"}}}, 4761 }, 4762 { 4763 // Test pending 4764 pv: api.PersistentVolume{ 4765 ObjectMeta: metav1.ObjectMeta{ 4766 Name: "test3", 4767 }, 4768 Spec: api.PersistentVolumeSpec{ 4769 ClaimRef: &claimRef, 4770 AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteMany}, 4771 Capacity: map[api.ResourceName]resource.Quantity{ 4772 api.ResourceStorage: resource.MustParse("10Gi"), 4773 }, 4774 }, 4775 Status: api.PersistentVolumeStatus{ 4776 Phase: api.VolumePending, 4777 }, 4778 }, 4779 expected: []metav1.TableRow{{Cells: []interface{}{"test3", "10Gi", "RWX", "", "Pending", "default/test", "", "<unset>", "", "<unknown>", "<unset>"}}}, 4780 }, 4781 { 4782 // Test pending, storageClass 4783 pv: api.PersistentVolume{ 4784 ObjectMeta: metav1.ObjectMeta{ 4785 Name: "test4", 4786 }, 4787 Spec: api.PersistentVolumeSpec{ 4788 ClaimRef: &claimRef, 4789 StorageClassName: myScn, 4790 AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, 4791 Capacity: map[api.ResourceName]resource.Quantity{ 4792 api.ResourceStorage: resource.MustParse("10Gi"), 4793 }, 4794 }, 4795 Status: api.PersistentVolumeStatus{ 4796 Phase: api.VolumePending, 4797 }, 4798 }, 4799 expected: []metav1.TableRow{{Cells: []interface{}{"test4", "10Gi", "RWO", "", "Pending", "default/test", "my-scn", "<unset>", "", "<unknown>", "<unset>"}}}, 4800 }, 4801 { 4802 // Test pending, storageClass, volumeAttributesClass 4803 pv: api.PersistentVolume{ 4804 ObjectMeta: metav1.ObjectMeta{ 4805 Name: "test4", 4806 }, 4807 Spec: api.PersistentVolumeSpec{ 4808 ClaimRef: &claimRef, 4809 StorageClassName: myScn, 4810 VolumeAttributesClassName: &myVacn, 4811 AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, 4812 Capacity: map[api.ResourceName]resource.Quantity{ 4813 api.ResourceStorage: resource.MustParse("10Gi"), 4814 }, 4815 }, 4816 Status: api.PersistentVolumeStatus{ 4817 Phase: api.VolumePending, 4818 }, 4819 }, 4820 expected: []metav1.TableRow{{Cells: []interface{}{"test4", "10Gi", "RWO", "", "Pending", "default/test", "my-scn", "my-vacn", "", "<unknown>", "<unset>"}}}, 4821 }, 4822 { 4823 // Test available 4824 pv: api.PersistentVolume{ 4825 ObjectMeta: metav1.ObjectMeta{ 4826 Name: "test5", 4827 }, 4828 Spec: api.PersistentVolumeSpec{ 4829 ClaimRef: &claimRef, 4830 StorageClassName: myScn, 4831 AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, 4832 Capacity: map[api.ResourceName]resource.Quantity{ 4833 api.ResourceStorage: resource.MustParse("10Gi"), 4834 }, 4835 }, 4836 Status: api.PersistentVolumeStatus{ 4837 Phase: api.VolumeAvailable, 4838 }, 4839 }, 4840 expected: []metav1.TableRow{{Cells: []interface{}{"test5", "10Gi", "RWO", "", "Available", "default/test", "my-scn", "<unset>", "", "<unknown>", "<unset>"}}}, 4841 }, 4842 { 4843 // Test released 4844 pv: api.PersistentVolume{ 4845 ObjectMeta: metav1.ObjectMeta{ 4846 Name: "test6", 4847 }, 4848 Spec: api.PersistentVolumeSpec{ 4849 ClaimRef: &claimRef, 4850 StorageClassName: myScn, 4851 AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, 4852 Capacity: map[api.ResourceName]resource.Quantity{ 4853 api.ResourceStorage: resource.MustParse("10Gi"), 4854 }, 4855 }, 4856 Status: api.PersistentVolumeStatus{ 4857 Phase: api.VolumeReleased, 4858 }, 4859 }, 4860 expected: []metav1.TableRow{{Cells: []interface{}{"test6", "10Gi", "RWO", "", "Released", "default/test", "my-scn", "<unset>", "", "<unknown>", "<unset>"}}}, 4861 }, 4862 } 4863 4864 for i, test := range tests { 4865 rows, err := printPersistentVolume(&test.pv, printers.GenerateOptions{}) 4866 if err != nil { 4867 t.Fatal(err) 4868 } 4869 for i := range rows { 4870 rows[i].Object.Object = nil 4871 } 4872 if !reflect.DeepEqual(test.expected, rows) { 4873 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 4874 } 4875 } 4876 } 4877 4878 func TestPrintPersistentVolumeClaim(t *testing.T) { 4879 volumeMode := api.PersistentVolumeFilesystem 4880 myVacn := "my-vacn" 4881 myScn := "my-scn" 4882 tests := []struct { 4883 pvc api.PersistentVolumeClaim 4884 expected []metav1.TableRow 4885 }{ 4886 { 4887 // Test name, num of containers, restarts, container ready status 4888 pvc: api.PersistentVolumeClaim{ 4889 ObjectMeta: metav1.ObjectMeta{ 4890 Name: "test1", 4891 }, 4892 Spec: api.PersistentVolumeClaimSpec{ 4893 VolumeName: "my-volume", 4894 VolumeMode: &volumeMode, 4895 }, 4896 Status: api.PersistentVolumeClaimStatus{ 4897 Phase: api.ClaimBound, 4898 AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany}, 4899 Capacity: map[api.ResourceName]resource.Quantity{ 4900 api.ResourceStorage: resource.MustParse("4Gi"), 4901 }, 4902 }, 4903 }, 4904 expected: []metav1.TableRow{{Cells: []interface{}{"test1", "Bound", "my-volume", "4Gi", "ROX", "", "<unset>", "<unknown>", "Filesystem"}}}, 4905 }, 4906 { 4907 // Test name, num of containers, restarts, container ready status 4908 pvc: api.PersistentVolumeClaim{ 4909 ObjectMeta: metav1.ObjectMeta{ 4910 Name: "test2", 4911 }, 4912 Spec: api.PersistentVolumeClaimSpec{ 4913 VolumeMode: &volumeMode, 4914 }, 4915 Status: api.PersistentVolumeClaimStatus{ 4916 Phase: api.ClaimLost, 4917 AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany}, 4918 Capacity: map[api.ResourceName]resource.Quantity{ 4919 api.ResourceStorage: resource.MustParse("4Gi"), 4920 }, 4921 }, 4922 }, 4923 expected: []metav1.TableRow{{Cells: []interface{}{"test2", "Lost", "", "", "", "", "<unset>", "<unknown>", "Filesystem"}}}, 4924 }, 4925 { 4926 // Test name, num of containers, restarts, container ready status 4927 pvc: api.PersistentVolumeClaim{ 4928 ObjectMeta: metav1.ObjectMeta{ 4929 Name: "test3", 4930 }, 4931 Spec: api.PersistentVolumeClaimSpec{ 4932 VolumeName: "my-volume", 4933 VolumeMode: &volumeMode, 4934 }, 4935 Status: api.PersistentVolumeClaimStatus{ 4936 Phase: api.ClaimPending, 4937 AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteMany}, 4938 Capacity: map[api.ResourceName]resource.Quantity{ 4939 api.ResourceStorage: resource.MustParse("10Gi"), 4940 }, 4941 }, 4942 }, 4943 expected: []metav1.TableRow{{Cells: []interface{}{"test3", "Pending", "my-volume", "10Gi", "RWX", "", "<unset>", "<unknown>", "Filesystem"}}}, 4944 }, 4945 { 4946 // Test name, num of containers, restarts, container ready status 4947 pvc: api.PersistentVolumeClaim{ 4948 ObjectMeta: metav1.ObjectMeta{ 4949 Name: "test4", 4950 }, 4951 Spec: api.PersistentVolumeClaimSpec{ 4952 VolumeName: "my-volume", 4953 StorageClassName: &myScn, 4954 VolumeMode: &volumeMode, 4955 }, 4956 Status: api.PersistentVolumeClaimStatus{ 4957 Phase: api.ClaimPending, 4958 AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, 4959 Capacity: map[api.ResourceName]resource.Quantity{ 4960 api.ResourceStorage: resource.MustParse("10Gi"), 4961 }, 4962 }, 4963 }, 4964 expected: []metav1.TableRow{{Cells: []interface{}{"test4", "Pending", "my-volume", "10Gi", "RWO", "my-scn", "<unset>", "<unknown>", "Filesystem"}}}, 4965 }, 4966 { 4967 // Test name, num of containers, restarts, container ready status 4968 pvc: api.PersistentVolumeClaim{ 4969 ObjectMeta: metav1.ObjectMeta{ 4970 Name: "test5", 4971 }, 4972 Spec: api.PersistentVolumeClaimSpec{ 4973 VolumeName: "my-volume", 4974 StorageClassName: &myScn, 4975 }, 4976 Status: api.PersistentVolumeClaimStatus{ 4977 Phase: api.ClaimPending, 4978 AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, 4979 Capacity: map[api.ResourceName]resource.Quantity{ 4980 api.ResourceStorage: resource.MustParse("10Gi"), 4981 }, 4982 }, 4983 }, 4984 expected: []metav1.TableRow{{Cells: []interface{}{"test5", "Pending", "my-volume", "10Gi", "RWO", "my-scn", "<unset>", "<unknown>", "<unset>"}}}, 4985 }, 4986 { 4987 // Test name, num of containers, restarts, container ready status 4988 pvc: api.PersistentVolumeClaim{ 4989 ObjectMeta: metav1.ObjectMeta{ 4990 Name: "test5", 4991 }, 4992 Spec: api.PersistentVolumeClaimSpec{ 4993 VolumeName: "my-volume", 4994 StorageClassName: &myScn, 4995 VolumeAttributesClassName: &myVacn, 4996 }, 4997 Status: api.PersistentVolumeClaimStatus{ 4998 Phase: api.ClaimPending, 4999 AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, 5000 Capacity: map[api.ResourceName]resource.Quantity{ 5001 api.ResourceStorage: resource.MustParse("10Gi"), 5002 }, 5003 }, 5004 }, 5005 expected: []metav1.TableRow{{Cells: []interface{}{"test5", "Pending", "my-volume", "10Gi", "RWO", "my-scn", "my-vacn", "<unknown>", "<unset>"}}}, 5006 }, 5007 } 5008 5009 for i, test := range tests { 5010 rows, err := printPersistentVolumeClaim(&test.pvc, printers.GenerateOptions{}) 5011 if err != nil { 5012 t.Fatal(err) 5013 } 5014 for i := range rows { 5015 rows[i].Object.Object = nil 5016 } 5017 if !reflect.DeepEqual(test.expected, rows) { 5018 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 5019 } 5020 } 5021 } 5022 5023 func TestPrintComponentStatus(t *testing.T) { 5024 tests := []struct { 5025 componentStatus api.ComponentStatus 5026 expected []metav1.TableRow 5027 }{ 5028 // Basic component status without conditions 5029 { 5030 componentStatus: api.ComponentStatus{ 5031 ObjectMeta: metav1.ObjectMeta{ 5032 Name: "cs1", 5033 }, 5034 Conditions: []api.ComponentCondition{}, 5035 }, 5036 // Columns: Name, Status, Message, Error 5037 expected: []metav1.TableRow{{Cells: []interface{}{"cs1", "Unknown", "", ""}}}, 5038 }, 5039 // Basic component status with healthy condition. 5040 { 5041 componentStatus: api.ComponentStatus{ 5042 ObjectMeta: metav1.ObjectMeta{ 5043 Name: "cs2", 5044 }, 5045 Conditions: []api.ComponentCondition{ 5046 { 5047 Type: "Healthy", 5048 Status: api.ConditionTrue, 5049 Message: "test message", 5050 Error: "test error", 5051 }, 5052 }, 5053 }, 5054 // Columns: Name, Status, Message, Error 5055 expected: []metav1.TableRow{{Cells: []interface{}{"cs2", "Healthy", "test message", "test error"}}}, 5056 }, 5057 // Basic component status with healthy condition. 5058 { 5059 componentStatus: api.ComponentStatus{ 5060 ObjectMeta: metav1.ObjectMeta{ 5061 Name: "cs3", 5062 }, 5063 Conditions: []api.ComponentCondition{ 5064 { 5065 Type: "Healthy", 5066 Status: api.ConditionFalse, 5067 Message: "test message", 5068 Error: "test error", 5069 }, 5070 }, 5071 }, 5072 // Columns: Name, Status, Message, Error 5073 expected: []metav1.TableRow{{Cells: []interface{}{"cs3", "Unhealthy", "test message", "test error"}}}, 5074 }, 5075 } 5076 5077 for i, test := range tests { 5078 rows, err := printComponentStatus(&test.componentStatus, printers.GenerateOptions{}) 5079 if err != nil { 5080 t.Fatal(err) 5081 } 5082 for i := range rows { 5083 rows[i].Object.Object = nil 5084 } 5085 if !reflect.DeepEqual(test.expected, rows) { 5086 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 5087 } 5088 } 5089 } 5090 5091 func TestPrintCronJob(t *testing.T) { 5092 completions := int32(2) 5093 suspend := false 5094 tests := []struct { 5095 cronjob batch.CronJob 5096 options printers.GenerateOptions 5097 expected []metav1.TableRow 5098 }{ 5099 // Basic cron job; does not print containers, images, or labels. 5100 { 5101 cronjob: batch.CronJob{ 5102 ObjectMeta: metav1.ObjectMeta{ 5103 Name: "cronjob1", 5104 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5105 }, 5106 Spec: batch.CronJobSpec{ 5107 Schedule: "0/5 * * * ?", 5108 Suspend: &suspend, 5109 JobTemplate: batch.JobTemplateSpec{ 5110 Spec: batch.JobSpec{ 5111 Completions: &completions, 5112 Template: api.PodTemplateSpec{ 5113 Spec: api.PodSpec{ 5114 Containers: []api.Container{ 5115 { 5116 Name: "fake-job-container1", 5117 Image: "fake-job-image1", 5118 }, 5119 { 5120 Name: "fake-job-container2", 5121 Image: "fake-job-image2", 5122 }, 5123 }, 5124 }, 5125 }, 5126 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, 5127 }, 5128 }, 5129 }, 5130 Status: batch.CronJobStatus{ 5131 LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)}, 5132 }, 5133 }, 5134 options: printers.GenerateOptions{}, 5135 // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age 5136 expected: []metav1.TableRow{{Cells: []interface{}{"cronjob1", "0/5 * * * ?", "False", int64(0), "0s", "0s"}}}, 5137 }, 5138 // Generate options: Wide; prints containers, images, and labels. 5139 { 5140 cronjob: batch.CronJob{ 5141 ObjectMeta: metav1.ObjectMeta{ 5142 Name: "cronjob1", 5143 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5144 }, 5145 Spec: batch.CronJobSpec{ 5146 Schedule: "0/5 * * * ?", 5147 Suspend: &suspend, 5148 JobTemplate: batch.JobTemplateSpec{ 5149 Spec: batch.JobSpec{ 5150 Completions: &completions, 5151 Template: api.PodTemplateSpec{ 5152 Spec: api.PodSpec{ 5153 Containers: []api.Container{ 5154 { 5155 Name: "fake-job-container1", 5156 Image: "fake-job-image1", 5157 }, 5158 { 5159 Name: "fake-job-container2", 5160 Image: "fake-job-image2", 5161 }, 5162 }, 5163 }, 5164 }, 5165 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, 5166 }, 5167 }, 5168 }, 5169 Status: batch.CronJobStatus{ 5170 LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)}, 5171 }, 5172 }, 5173 options: printers.GenerateOptions{Wide: true}, 5174 // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age 5175 expected: []metav1.TableRow{{Cells: []interface{}{"cronjob1", "0/5 * * * ?", "False", int64(0), "0s", "0s", "fake-job-container1,fake-job-container2", "fake-job-image1,fake-job-image2", "a=b"}}}, 5176 }, 5177 // CronJob with Last Schedule and Age 5178 { 5179 cronjob: batch.CronJob{ 5180 ObjectMeta: metav1.ObjectMeta{ 5181 Name: "cronjob2", 5182 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5183 }, 5184 Spec: batch.CronJobSpec{ 5185 Schedule: "0/5 * * * ?", 5186 Suspend: &suspend, 5187 }, 5188 Status: batch.CronJobStatus{ 5189 LastScheduleTime: &metav1.Time{Time: time.Now().Add(-3e10)}, 5190 }, 5191 }, 5192 options: printers.GenerateOptions{}, 5193 // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age 5194 expected: []metav1.TableRow{{Cells: []interface{}{"cronjob2", "0/5 * * * ?", "False", int64(0), "30s", "5m"}}}, 5195 }, 5196 // CronJob without Last Schedule 5197 { 5198 cronjob: batch.CronJob{ 5199 ObjectMeta: metav1.ObjectMeta{ 5200 Name: "cronjob3", 5201 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5202 }, 5203 Spec: batch.CronJobSpec{ 5204 Schedule: "0/5 * * * ?", 5205 Suspend: &suspend, 5206 }, 5207 Status: batch.CronJobStatus{}, 5208 }, 5209 options: printers.GenerateOptions{}, 5210 // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age 5211 expected: []metav1.TableRow{{Cells: []interface{}{"cronjob3", "0/5 * * * ?", "False", int64(0), "<none>", "5m"}}}, 5212 }, 5213 } 5214 5215 for i, test := range tests { 5216 rows, err := printCronJob(&test.cronjob, test.options) 5217 if err != nil { 5218 t.Fatal(err) 5219 } 5220 for i := range rows { 5221 rows[i].Object.Object = nil 5222 } 5223 if !reflect.DeepEqual(test.expected, rows) { 5224 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 5225 } 5226 } 5227 } 5228 5229 func TestPrintCronJobList(t *testing.T) { 5230 completions := int32(2) 5231 suspend := false 5232 5233 cronJobList := batch.CronJobList{ 5234 Items: []batch.CronJob{ 5235 { 5236 ObjectMeta: metav1.ObjectMeta{ 5237 Name: "cronjob1", 5238 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5239 }, 5240 Spec: batch.CronJobSpec{ 5241 Schedule: "0/5 * * * ?", 5242 Suspend: &suspend, 5243 JobTemplate: batch.JobTemplateSpec{ 5244 Spec: batch.JobSpec{ 5245 Completions: &completions, 5246 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, 5247 }, 5248 }, 5249 }, 5250 Status: batch.CronJobStatus{ 5251 LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)}, 5252 }, 5253 }, 5254 { 5255 ObjectMeta: metav1.ObjectMeta{ 5256 Name: "cronjob2", 5257 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5258 }, 5259 Spec: batch.CronJobSpec{ 5260 Schedule: "4/5 1 1 1 ?", 5261 Suspend: &suspend, 5262 JobTemplate: batch.JobTemplateSpec{ 5263 Spec: batch.JobSpec{ 5264 Completions: &completions, 5265 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, 5266 }, 5267 }, 5268 }, 5269 Status: batch.CronJobStatus{ 5270 LastScheduleTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)}, 5271 }, 5272 }, 5273 }, 5274 } 5275 5276 // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age 5277 expectedRows := []metav1.TableRow{ 5278 {Cells: []interface{}{"cronjob1", "0/5 * * * ?", "False", int64(0), "0s", "0s"}}, 5279 {Cells: []interface{}{"cronjob2", "4/5 1 1 1 ?", "False", int64(0), "20m", "0s"}}, 5280 } 5281 5282 rows, err := printCronJobList(&cronJobList, printers.GenerateOptions{}) 5283 if err != nil { 5284 t.Fatalf("Error printing job list: %#v", err) 5285 } 5286 for i := range rows { 5287 rows[i].Object.Object = nil 5288 } 5289 if !reflect.DeepEqual(expectedRows, rows) { 5290 t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) 5291 } 5292 } 5293 5294 func TestPrintStorageClass(t *testing.T) { 5295 policyDelte := api.PersistentVolumeReclaimDelete 5296 policyRetain := api.PersistentVolumeReclaimRetain 5297 bindModeImmediate := storage.VolumeBindingImmediate 5298 bindModeWait := storage.VolumeBindingWaitForFirstConsumer 5299 tests := []struct { 5300 sc storage.StorageClass 5301 expected []metav1.TableRow 5302 }{ 5303 { 5304 sc: storage.StorageClass{ 5305 ObjectMeta: metav1.ObjectMeta{ 5306 Name: "sc1", 5307 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5308 }, 5309 Provisioner: "kubernetes.io/glusterfs", 5310 }, 5311 expected: []metav1.TableRow{{Cells: []interface{}{"sc1", "kubernetes.io/glusterfs", "Delete", 5312 "Immediate", false, "0s"}}}, 5313 }, 5314 { 5315 sc: storage.StorageClass{ 5316 ObjectMeta: metav1.ObjectMeta{ 5317 Name: "sc2", 5318 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5319 }, 5320 Provisioner: "kubernetes.io/nfs", 5321 }, 5322 expected: []metav1.TableRow{{Cells: []interface{}{"sc2", "kubernetes.io/nfs", "Delete", 5323 "Immediate", false, "5m"}}}, 5324 }, 5325 { 5326 sc: storage.StorageClass{ 5327 ObjectMeta: metav1.ObjectMeta{ 5328 Name: "sc3", 5329 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5330 }, 5331 Provisioner: "kubernetes.io/nfs", 5332 ReclaimPolicy: &policyDelte, 5333 }, 5334 expected: []metav1.TableRow{{Cells: []interface{}{"sc3", "kubernetes.io/nfs", "Delete", 5335 "Immediate", false, "5m"}}}, 5336 }, 5337 { 5338 sc: storage.StorageClass{ 5339 ObjectMeta: metav1.ObjectMeta{ 5340 Name: "sc4", 5341 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5342 }, 5343 Provisioner: "kubernetes.io/nfs", 5344 ReclaimPolicy: &policyRetain, 5345 VolumeBindingMode: &bindModeImmediate, 5346 }, 5347 expected: []metav1.TableRow{{Cells: []interface{}{"sc4", "kubernetes.io/nfs", "Retain", 5348 "Immediate", false, "5m"}}}, 5349 }, 5350 { 5351 sc: storage.StorageClass{ 5352 ObjectMeta: metav1.ObjectMeta{ 5353 Name: "sc5", 5354 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5355 }, 5356 Provisioner: "kubernetes.io/nfs", 5357 ReclaimPolicy: &policyRetain, 5358 VolumeBindingMode: &bindModeWait, 5359 }, 5360 expected: []metav1.TableRow{{Cells: []interface{}{"sc5", "kubernetes.io/nfs", "Retain", 5361 "WaitForFirstConsumer", false, "5m"}}}, 5362 }, 5363 { 5364 sc: storage.StorageClass{ 5365 ObjectMeta: metav1.ObjectMeta{ 5366 Name: "sc6", 5367 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5368 }, 5369 Provisioner: "kubernetes.io/nfs", 5370 ReclaimPolicy: &policyRetain, 5371 AllowVolumeExpansion: boolP(true), 5372 VolumeBindingMode: &bindModeWait, 5373 }, 5374 expected: []metav1.TableRow{{Cells: []interface{}{"sc6", "kubernetes.io/nfs", "Retain", 5375 "WaitForFirstConsumer", true, "5m"}}}, 5376 }, 5377 } 5378 5379 for i, test := range tests { 5380 rows, err := printStorageClass(&test.sc, printers.GenerateOptions{}) 5381 if err != nil { 5382 t.Fatal(err) 5383 } 5384 for i := range rows { 5385 rows[i].Object.Object = nil 5386 } 5387 if !reflect.DeepEqual(test.expected, rows) { 5388 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 5389 } 5390 } 5391 } 5392 5393 func TestPrintVolumeAttributesClass(t *testing.T) { 5394 tests := []struct { 5395 vac storage.VolumeAttributesClass 5396 expected []metav1.TableRow 5397 }{ 5398 { 5399 vac: storage.VolumeAttributesClass{ 5400 ObjectMeta: metav1.ObjectMeta{ 5401 Name: "vac1", 5402 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5403 }, 5404 DriverName: "fake", 5405 }, 5406 expected: []metav1.TableRow{{Cells: []interface{}{"vac1", "fake", "0s"}}}, 5407 }, 5408 { 5409 vac: storage.VolumeAttributesClass{ 5410 ObjectMeta: metav1.ObjectMeta{ 5411 Name: "vac2", 5412 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5413 }, 5414 DriverName: "fake", 5415 Parameters: map[string]string{ 5416 "iops": "500", 5417 "throughput": "50MiB/s", 5418 }, 5419 }, 5420 expected: []metav1.TableRow{{Cells: []interface{}{"vac2", "fake", "5m"}}}, 5421 }, 5422 } 5423 5424 for i, test := range tests { 5425 rows, err := printVolumeAttributesClass(&test.vac, printers.GenerateOptions{}) 5426 if err != nil { 5427 t.Fatal(err) 5428 } 5429 for i := range rows { 5430 rows[i].Object.Object = nil 5431 } 5432 if !reflect.DeepEqual(test.expected, rows) { 5433 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 5434 } 5435 } 5436 } 5437 5438 func TestPrintLease(t *testing.T) { 5439 holder1 := "holder1" 5440 holder2 := "holder2" 5441 tests := []struct { 5442 lease coordination.Lease 5443 expected []metav1.TableRow 5444 }{ 5445 { 5446 lease: coordination.Lease{ 5447 ObjectMeta: metav1.ObjectMeta{ 5448 Name: "lease1", 5449 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5450 }, 5451 Spec: coordination.LeaseSpec{ 5452 HolderIdentity: &holder1, 5453 }, 5454 }, 5455 expected: []metav1.TableRow{{Cells: []interface{}{"lease1", "holder1", "0s"}}}, 5456 }, 5457 { 5458 lease: coordination.Lease{ 5459 ObjectMeta: metav1.ObjectMeta{ 5460 Name: "lease2", 5461 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5462 }, 5463 Spec: coordination.LeaseSpec{ 5464 HolderIdentity: &holder2, 5465 }, 5466 }, 5467 expected: []metav1.TableRow{{Cells: []interface{}{"lease2", "holder2", "5m"}}}, 5468 }, 5469 } 5470 5471 for i, test := range tests { 5472 rows, err := printLease(&test.lease, printers.GenerateOptions{}) 5473 if err != nil { 5474 t.Fatal(err) 5475 } 5476 for i := range rows { 5477 rows[i].Object.Object = nil 5478 } 5479 if !reflect.DeepEqual(test.expected, rows) { 5480 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 5481 } 5482 } 5483 } 5484 5485 func TestPrintPriorityClass(t *testing.T) { 5486 tests := []struct { 5487 pc scheduling.PriorityClass 5488 expected []metav1.TableRow 5489 }{ 5490 { 5491 pc: scheduling.PriorityClass{ 5492 ObjectMeta: metav1.ObjectMeta{ 5493 Name: "pc1", 5494 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5495 }, 5496 Value: 1, 5497 }, 5498 expected: []metav1.TableRow{{Cells: []interface{}{"pc1", int64(1), bool(false), "0s"}}}, 5499 }, 5500 { 5501 pc: scheduling.PriorityClass{ 5502 ObjectMeta: metav1.ObjectMeta{ 5503 Name: "pc2", 5504 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5505 }, 5506 Value: 1000000000, 5507 GlobalDefault: true, 5508 }, 5509 expected: []metav1.TableRow{{Cells: []interface{}{"pc2", int64(1000000000), bool(true), "5m"}}}, 5510 }, 5511 } 5512 5513 for i, test := range tests { 5514 rows, err := printPriorityClass(&test.pc, printers.GenerateOptions{}) 5515 if err != nil { 5516 t.Fatal(err) 5517 } 5518 for i := range rows { 5519 rows[i].Object.Object = nil 5520 } 5521 if !reflect.DeepEqual(test.expected, rows) { 5522 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 5523 } 5524 } 5525 } 5526 5527 func TestPrintRuntimeClass(t *testing.T) { 5528 tests := []struct { 5529 rc nodeapi.RuntimeClass 5530 expected []metav1.TableRow 5531 }{ 5532 { 5533 rc: nodeapi.RuntimeClass{ 5534 ObjectMeta: metav1.ObjectMeta{ 5535 Name: "rc1", 5536 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5537 }, 5538 Handler: "h1", 5539 }, 5540 expected: []metav1.TableRow{{Cells: []interface{}{"rc1", "h1", "0s"}}}, 5541 }, 5542 { 5543 rc: nodeapi.RuntimeClass{ 5544 ObjectMeta: metav1.ObjectMeta{ 5545 Name: "rc2", 5546 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5547 }, 5548 Handler: "h2", 5549 }, 5550 expected: []metav1.TableRow{{Cells: []interface{}{"rc2", "h2", "5m"}}}, 5551 }, 5552 } 5553 5554 for i, test := range tests { 5555 rows, err := printRuntimeClass(&test.rc, printers.GenerateOptions{}) 5556 if err != nil { 5557 t.Fatal(err) 5558 } 5559 for i := range rows { 5560 rows[i].Object.Object = nil 5561 } 5562 if !reflect.DeepEqual(test.expected, rows) { 5563 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 5564 } 5565 } 5566 } 5567 5568 func TestPrintEndpoint(t *testing.T) { 5569 5570 tests := []struct { 5571 endpoint api.Endpoints 5572 expected []metav1.TableRow 5573 }{ 5574 // Basic endpoint with no IP's 5575 { 5576 endpoint: api.Endpoints{ 5577 ObjectMeta: metav1.ObjectMeta{ 5578 Name: "endpoint1", 5579 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5580 }, 5581 }, 5582 // Columns: Name, Endpoints, Age 5583 expected: []metav1.TableRow{{Cells: []interface{}{"endpoint1", "<none>", "0s"}}}, 5584 }, 5585 // Endpoint with no ports 5586 { 5587 endpoint: api.Endpoints{ 5588 ObjectMeta: metav1.ObjectMeta{ 5589 Name: "endpoint3", 5590 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5591 }, 5592 Subsets: []api.EndpointSubset{ 5593 { 5594 Addresses: []api.EndpointAddress{ 5595 { 5596 IP: "1.2.3.4", 5597 }, 5598 { 5599 IP: "5.6.7.8", 5600 }, 5601 }, 5602 }, 5603 }, 5604 }, 5605 // Columns: Name, Endpoints, Age 5606 expected: []metav1.TableRow{{Cells: []interface{}{"endpoint3", "1.2.3.4,5.6.7.8", "5m"}}}, 5607 }, 5608 // Basic endpoint with two IP's and one port 5609 { 5610 endpoint: api.Endpoints{ 5611 ObjectMeta: metav1.ObjectMeta{ 5612 Name: "endpoint2", 5613 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5614 }, 5615 Subsets: []api.EndpointSubset{ 5616 { 5617 Addresses: []api.EndpointAddress{ 5618 { 5619 IP: "1.2.3.4", 5620 }, 5621 { 5622 IP: "5.6.7.8", 5623 }, 5624 }, 5625 Ports: []api.EndpointPort{ 5626 { 5627 Port: 8001, 5628 Protocol: "tcp", 5629 }, 5630 }, 5631 }, 5632 }, 5633 }, 5634 // Columns: Name, Endpoints, Age 5635 expected: []metav1.TableRow{{Cells: []interface{}{"endpoint2", "1.2.3.4:8001,5.6.7.8:8001", "0s"}}}, 5636 }, 5637 // Basic endpoint with greater than three IP's triggering "more" string 5638 { 5639 endpoint: api.Endpoints{ 5640 ObjectMeta: metav1.ObjectMeta{ 5641 Name: "endpoint2", 5642 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5643 }, 5644 Subsets: []api.EndpointSubset{ 5645 { 5646 Addresses: []api.EndpointAddress{ 5647 { 5648 IP: "1.2.3.4", 5649 }, 5650 { 5651 IP: "5.6.7.8", 5652 }, 5653 { 5654 IP: "9.8.7.6", 5655 }, 5656 { 5657 IP: "6.6.6.6", 5658 }, 5659 }, 5660 Ports: []api.EndpointPort{ 5661 { 5662 Port: 8001, 5663 Protocol: "tcp", 5664 }, 5665 }, 5666 }, 5667 }, 5668 }, 5669 // Columns: Name, Endpoints, Age 5670 expected: []metav1.TableRow{{Cells: []interface{}{"endpoint2", "1.2.3.4:8001,5.6.7.8:8001,9.8.7.6:8001 + 1 more...", "0s"}}}, 5671 }, 5672 } 5673 5674 for i, test := range tests { 5675 rows, err := printEndpoints(&test.endpoint, printers.GenerateOptions{}) 5676 if err != nil { 5677 t.Fatal(err) 5678 } 5679 for i := range rows { 5680 rows[i].Object.Object = nil 5681 } 5682 if !reflect.DeepEqual(test.expected, rows) { 5683 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 5684 } 5685 } 5686 5687 } 5688 5689 func TestPrintEndpointSlice(t *testing.T) { 5690 tcpProtocol := api.ProtocolTCP 5691 5692 tests := []struct { 5693 endpointSlice discovery.EndpointSlice 5694 expected []metav1.TableRow 5695 }{ 5696 { 5697 endpointSlice: discovery.EndpointSlice{ 5698 ObjectMeta: metav1.ObjectMeta{ 5699 Name: "abcslice.123", 5700 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5701 }, 5702 AddressType: discovery.AddressTypeIPv4, 5703 Ports: []discovery.EndpointPort{{ 5704 Name: utilpointer.StringPtr("http"), 5705 Port: utilpointer.Int32Ptr(80), 5706 Protocol: &tcpProtocol, 5707 }}, 5708 Endpoints: []discovery.Endpoint{{ 5709 Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"}, 5710 }}, 5711 }, 5712 // Columns: Name, AddressType, Ports, Endpoints, Age 5713 expected: []metav1.TableRow{{Cells: []interface{}{"abcslice.123", "IPv4", "80", "10.1.2.3,2001:db8::1234:5678", "0s"}}}, 5714 }, { 5715 endpointSlice: discovery.EndpointSlice{ 5716 ObjectMeta: metav1.ObjectMeta{ 5717 Name: "longerslicename.123", 5718 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5719 }, 5720 AddressType: discovery.AddressTypeIPv6, 5721 Ports: []discovery.EndpointPort{{ 5722 Name: utilpointer.StringPtr("http"), 5723 Port: utilpointer.Int32Ptr(80), 5724 Protocol: &tcpProtocol, 5725 }, { 5726 Name: utilpointer.StringPtr("https"), 5727 Port: utilpointer.Int32Ptr(443), 5728 Protocol: &tcpProtocol, 5729 }}, 5730 Endpoints: []discovery.Endpoint{{ 5731 Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"}, 5732 }, { 5733 Addresses: []string{"10.2.3.4", "2001:db8::2345:6789"}, 5734 }}, 5735 }, 5736 // Columns: Name, AddressType, Ports, Endpoints, Age 5737 expected: []metav1.TableRow{{Cells: []interface{}{"longerslicename.123", "IPv6", "80,443", "10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more...", "5m"}}}, 5738 }, { 5739 endpointSlice: discovery.EndpointSlice{ 5740 ObjectMeta: metav1.ObjectMeta{ 5741 Name: "multiportslice.123", 5742 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5743 }, 5744 AddressType: discovery.AddressTypeIPv4, 5745 Ports: []discovery.EndpointPort{{ 5746 Name: utilpointer.StringPtr("http"), 5747 Port: utilpointer.Int32Ptr(80), 5748 Protocol: &tcpProtocol, 5749 }, { 5750 Name: utilpointer.StringPtr("https"), 5751 Port: utilpointer.Int32Ptr(443), 5752 Protocol: &tcpProtocol, 5753 }, { 5754 Name: utilpointer.StringPtr("extra1"), 5755 Port: utilpointer.Int32Ptr(3000), 5756 Protocol: &tcpProtocol, 5757 }, { 5758 Name: utilpointer.StringPtr("extra2"), 5759 Port: utilpointer.Int32Ptr(3001), 5760 Protocol: &tcpProtocol, 5761 }}, 5762 Endpoints: []discovery.Endpoint{{ 5763 Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"}, 5764 }, { 5765 Addresses: []string{"10.2.3.4", "2001:db8::2345:6789"}, 5766 }}, 5767 }, 5768 // Columns: Name, AddressType, Ports, Endpoints, Age 5769 expected: []metav1.TableRow{{Cells: []interface{}{"multiportslice.123", "IPv4", "80,443,3000 + 1 more...", "10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more...", "5m"}}}, 5770 }, 5771 } 5772 5773 for i, test := range tests { 5774 rows, err := printEndpointSlice(&test.endpointSlice, printers.GenerateOptions{}) 5775 if err != nil { 5776 t.Fatal(err) 5777 } 5778 for i := range rows { 5779 rows[i].Object.Object = nil 5780 } 5781 if !reflect.DeepEqual(test.expected, rows) { 5782 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 5783 } 5784 } 5785 } 5786 5787 func TestPrintFlowSchema(t *testing.T) { 5788 all := []string{"*"} 5789 5790 tests := []struct { 5791 fs flowcontrol.FlowSchema 5792 expected []metav1.TableRow 5793 }{ 5794 { 5795 fs: flowcontrol.FlowSchema{ 5796 ObjectMeta: metav1.ObjectMeta{ 5797 Name: "all-matcher", 5798 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5799 }, 5800 Spec: flowcontrol.FlowSchemaSpec{ 5801 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{Name: "allee"}, 5802 MatchingPrecedence: math.MaxInt32, 5803 DistinguisherMethod: &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByUserType}, 5804 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 5805 Subjects: []flowcontrol.Subject{{ 5806 Kind: flowcontrol.SubjectKindGroup, 5807 Group: &flowcontrol.GroupSubject{Name: "system:authenticated"}, 5808 }}, 5809 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 5810 Verbs: all, 5811 APIGroups: all, 5812 Resources: all, 5813 ClusterScope: true, 5814 Namespaces: all, 5815 }}, 5816 }, { 5817 Subjects: []flowcontrol.Subject{{ 5818 Kind: flowcontrol.SubjectKindGroup, 5819 Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"}, 5820 }}, 5821 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 5822 Verbs: all, 5823 APIGroups: all, 5824 Resources: all, 5825 ClusterScope: true, 5826 Namespaces: all, 5827 }}, 5828 }, { 5829 Subjects: []flowcontrol.Subject{{ 5830 Kind: flowcontrol.SubjectKindGroup, 5831 Group: &flowcontrol.GroupSubject{Name: "system:authenticated"}, 5832 }, { 5833 Kind: flowcontrol.SubjectKindGroup, 5834 Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"}, 5835 }}, 5836 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 5837 Verbs: all, 5838 NonResourceURLs: all, 5839 }}, 5840 }}, 5841 }, 5842 }, 5843 // Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, Age, MissingPL 5844 expected: []metav1.TableRow{{Cells: []interface{}{"all-matcher", "allee", int64(math.MaxInt32), "ByUser", "0s", "?"}}}, 5845 }, { 5846 fs: flowcontrol.FlowSchema{ 5847 ObjectMeta: metav1.ObjectMeta{ 5848 Name: "some-matcher", 5849 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5850 }, 5851 Spec: flowcontrol.FlowSchemaSpec{ 5852 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{Name: "allee"}, 5853 MatchingPrecedence: 0, 5854 DistinguisherMethod: &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByNamespaceType}, 5855 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 5856 Subjects: []flowcontrol.Subject{{ 5857 Kind: flowcontrol.SubjectKindGroup, 5858 Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"}, 5859 }}, 5860 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 5861 Verbs: all, 5862 APIGroups: all, 5863 Resources: all, 5864 ClusterScope: true, 5865 Namespaces: all, 5866 }}, 5867 }, { 5868 Subjects: []flowcontrol.Subject{{ 5869 Kind: flowcontrol.SubjectKindGroup, 5870 Group: &flowcontrol.GroupSubject{Name: "system:authenticated"}, 5871 }, { 5872 Kind: flowcontrol.SubjectKindGroup, 5873 Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"}, 5874 }}, 5875 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 5876 Verbs: all, 5877 NonResourceURLs: all, 5878 }}, 5879 }}, 5880 }, 5881 Status: flowcontrol.FlowSchemaStatus{ 5882 Conditions: []flowcontrol.FlowSchemaCondition{{ 5883 Type: flowcontrol.FlowSchemaConditionDangling, 5884 Status: "True", 5885 LastTransitionTime: metav1.Time{Time: time.Now().Add(-time.Hour)}, 5886 }}, 5887 }, 5888 }, 5889 // Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, Age, MissingPL 5890 expected: []metav1.TableRow{{Cells: []interface{}{"some-matcher", "allee", int64(0), "ByNamespace", "5m", "True"}}}, 5891 }, { 5892 fs: flowcontrol.FlowSchema{ 5893 ObjectMeta: metav1.ObjectMeta{ 5894 Name: "exempt", 5895 CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, 5896 }, 5897 Spec: flowcontrol.FlowSchemaSpec{ 5898 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{Name: "allee"}, 5899 MatchingPrecedence: 0, 5900 DistinguisherMethod: nil, 5901 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 5902 Subjects: []flowcontrol.Subject{{ 5903 Kind: flowcontrol.SubjectKindGroup, 5904 Group: &flowcontrol.GroupSubject{Name: "system:masters"}, 5905 }}, 5906 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 5907 Verbs: all, 5908 APIGroups: all, 5909 Resources: all, 5910 ClusterScope: true, 5911 Namespaces: all, 5912 }}, 5913 }}, 5914 }, 5915 }, 5916 // Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, Age, MissingPL 5917 expected: []metav1.TableRow{{Cells: []interface{}{"exempt", "allee", int64(0), "<none>", "5m", "?"}}}, 5918 }, 5919 } 5920 5921 for i, test := range tests { 5922 rows, err := printFlowSchema(&test.fs, printers.GenerateOptions{}) 5923 if err != nil { 5924 t.Fatal(err) 5925 } 5926 for i := range rows { 5927 rows[i].Object.Object = nil 5928 } 5929 if !reflect.DeepEqual(test.expected, rows) { 5930 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 5931 } 5932 } 5933 } 5934 5935 func TestPrintPriorityLevelConfiguration(t *testing.T) { 5936 tests := []struct { 5937 pl flowcontrol.PriorityLevelConfiguration 5938 expected []metav1.TableRow 5939 }{ 5940 { 5941 pl: flowcontrol.PriorityLevelConfiguration{ 5942 ObjectMeta: metav1.ObjectMeta{ 5943 Name: "unlimited", 5944 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5945 }, 5946 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 5947 Type: flowcontrol.PriorityLevelEnablementExempt, 5948 }, 5949 }, 5950 // Columns: Name, Type, NominalConcurrencyShares, Queues, HandSize, QueueLengthLimit, Age 5951 expected: []metav1.TableRow{{Cells: []interface{}{"unlimited", "Exempt", "<none>", "<none>", "<none>", "<none>", "0s"}}}, 5952 }, 5953 { 5954 pl: flowcontrol.PriorityLevelConfiguration{ 5955 ObjectMeta: metav1.ObjectMeta{ 5956 Name: "unqueued", 5957 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5958 }, 5959 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 5960 Type: flowcontrol.PriorityLevelEnablementLimited, 5961 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 5962 NominalConcurrencyShares: 47, 5963 LimitResponse: flowcontrol.LimitResponse{ 5964 Type: flowcontrol.LimitResponseTypeReject, 5965 }, 5966 }, 5967 }, 5968 }, 5969 // Columns: Name, Type, NominalConcurrencyShares, Queues, HandSize, QueueLengthLimit, Age 5970 expected: []metav1.TableRow{{Cells: []interface{}{"unqueued", "Limited", int32(47), "<none>", "<none>", "<none>", "0s"}}}, 5971 }, 5972 { 5973 pl: flowcontrol.PriorityLevelConfiguration{ 5974 ObjectMeta: metav1.ObjectMeta{ 5975 Name: "queued", 5976 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 5977 }, 5978 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 5979 Type: flowcontrol.PriorityLevelEnablementLimited, 5980 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 5981 NominalConcurrencyShares: 42, 5982 LimitResponse: flowcontrol.LimitResponse{ 5983 Type: flowcontrol.LimitResponseTypeQueue, 5984 Queuing: &flowcontrol.QueuingConfiguration{ 5985 Queues: 8, 5986 HandSize: 3, 5987 QueueLengthLimit: 4, 5988 }, 5989 }, 5990 }, 5991 }, 5992 }, 5993 // Columns: Name, Type, NominalConcurrencyShares, Queues, HandSize, QueueLengthLimit, Age 5994 expected: []metav1.TableRow{{Cells: []interface{}{"queued", "Limited", int32(42), int32(8), int32(3), int32(4), "0s"}}}, 5995 }, 5996 } 5997 5998 for i, test := range tests { 5999 rows, err := printPriorityLevelConfiguration(&test.pl, printers.GenerateOptions{}) 6000 if err != nil { 6001 t.Fatal(err) 6002 } 6003 for i := range rows { 6004 rows[i].Object.Object = nil 6005 } 6006 if !reflect.DeepEqual(test.expected, rows) { 6007 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 6008 } 6009 } 6010 } 6011 6012 func TestPrintStorageVersion(t *testing.T) { 6013 commonEncodingVersion := "v1" 6014 tests := []struct { 6015 sv apiserverinternal.StorageVersion 6016 expected []metav1.TableRow 6017 }{ 6018 { 6019 sv: apiserverinternal.StorageVersion{ 6020 ObjectMeta: metav1.ObjectMeta{ 6021 Name: "empty", 6022 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 6023 }, 6024 Status: apiserverinternal.StorageVersionStatus{}, 6025 }, 6026 // Columns: Name, CommonEncodingVersion, StorageVersions, Age 6027 expected: []metav1.TableRow{{Cells: []interface{}{"empty", "<unset>", "<unset>", "0s"}}}, 6028 }, 6029 { 6030 sv: apiserverinternal.StorageVersion{ 6031 ObjectMeta: metav1.ObjectMeta{ 6032 Name: "valid", 6033 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 6034 }, 6035 Status: apiserverinternal.StorageVersionStatus{ 6036 StorageVersions: []apiserverinternal.ServerStorageVersion{ 6037 { 6038 APIServerID: "1", 6039 EncodingVersion: "v1", 6040 DecodableVersions: []string{"v1"}, 6041 }, 6042 { 6043 APIServerID: "2", 6044 EncodingVersion: "v1", 6045 DecodableVersions: []string{"v1", "v2"}, 6046 }, 6047 }, 6048 CommonEncodingVersion: &commonEncodingVersion, 6049 }, 6050 }, 6051 // Columns: Name, CommonEncodingVersion, StorageVersions, Age 6052 expected: []metav1.TableRow{{Cells: []interface{}{"valid", "v1", "1=v1,2=v1", "0s"}}}, 6053 }, 6054 { 6055 sv: apiserverinternal.StorageVersion{ 6056 ObjectMeta: metav1.ObjectMeta{ 6057 Name: "disagree", 6058 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 6059 }, 6060 Status: apiserverinternal.StorageVersionStatus{ 6061 StorageVersions: []apiserverinternal.ServerStorageVersion{ 6062 { 6063 APIServerID: "1", 6064 EncodingVersion: "v1", 6065 DecodableVersions: []string{"v1"}, 6066 }, 6067 { 6068 APIServerID: "2", 6069 EncodingVersion: "v1", 6070 DecodableVersions: []string{"v1", "v2"}, 6071 }, 6072 { 6073 APIServerID: "3", 6074 EncodingVersion: "v2", 6075 DecodableVersions: []string{"v2"}, 6076 }, 6077 }, 6078 }, 6079 }, 6080 // Columns: Name, CommonEncodingVersion, StorageVersions, Age 6081 expected: []metav1.TableRow{{Cells: []interface{}{"disagree", "<unset>", "1=v1,2=v1,3=v2", "0s"}}}, 6082 }, 6083 { 6084 sv: apiserverinternal.StorageVersion{ 6085 ObjectMeta: metav1.ObjectMeta{ 6086 Name: "agreeWithMore", 6087 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 6088 }, 6089 Status: apiserverinternal.StorageVersionStatus{ 6090 StorageVersions: []apiserverinternal.ServerStorageVersion{ 6091 { 6092 APIServerID: "1", 6093 EncodingVersion: "v1", 6094 DecodableVersions: []string{"v1"}, 6095 }, 6096 { 6097 APIServerID: "2", 6098 EncodingVersion: "v1", 6099 DecodableVersions: []string{"v1", "v2"}, 6100 }, 6101 { 6102 APIServerID: "3", 6103 EncodingVersion: "v1", 6104 DecodableVersions: []string{"v1", "v2"}, 6105 }, 6106 { 6107 APIServerID: "4", 6108 EncodingVersion: "v1", 6109 DecodableVersions: []string{"v1", "v2", "v3alpha1"}, 6110 }, 6111 }, 6112 CommonEncodingVersion: &commonEncodingVersion, 6113 }, 6114 }, 6115 // Columns: Name, CommonEncodingVersion, StorageVersions, Age 6116 expected: []metav1.TableRow{{Cells: []interface{}{"agreeWithMore", "v1", "1=v1,2=v1,3=v1 + 1 more...", "0s"}}}, 6117 }, 6118 } 6119 6120 for i, test := range tests { 6121 rows, err := printStorageVersion(&test.sv, printers.GenerateOptions{}) 6122 if err != nil { 6123 t.Fatal(err) 6124 } 6125 for i := range rows { 6126 rows[i].Object.Object = nil 6127 } 6128 if !reflect.DeepEqual(test.expected, rows) { 6129 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 6130 } 6131 } 6132 } 6133 6134 func TestPrintScale(t *testing.T) { 6135 tests := []struct { 6136 scale autoscaling.Scale 6137 options printers.GenerateOptions 6138 expected []metav1.TableRow 6139 }{ 6140 { 6141 scale: autoscaling.Scale{ 6142 ObjectMeta: metav1.ObjectMeta{ 6143 Name: "test-autoscaling", 6144 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, 6145 }, 6146 Spec: autoscaling.ScaleSpec{Replicas: 2}, 6147 Status: autoscaling.ScaleStatus{Replicas: 1}, 6148 }, 6149 expected: []metav1.TableRow{ 6150 { 6151 Cells: []interface{}{"test-autoscaling", int64(2), int64(1), string("0s")}, 6152 }, 6153 }, 6154 }, 6155 } 6156 6157 for i, test := range tests { 6158 rows, err := printScale(&test.scale, test.options) 6159 if err != nil { 6160 t.Fatal(err) 6161 } 6162 for i := range rows { 6163 rows[i].Object.Object = nil 6164 } 6165 if !reflect.DeepEqual(test.expected, rows) { 6166 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 6167 } 6168 } 6169 } 6170 6171 func TestTableRowDeepCopyShouldNotPanic(t *testing.T) { 6172 tests := []struct { 6173 name string 6174 printer func() ([]metav1.TableRow, error) 6175 }{ 6176 { 6177 name: "Pod", 6178 printer: func() ([]metav1.TableRow, error) { 6179 return printPod(&api.Pod{}, printers.GenerateOptions{}) 6180 }, 6181 }, 6182 { 6183 name: "PodTemplate", 6184 printer: func() ([]metav1.TableRow, error) { 6185 return printPodTemplate(&api.PodTemplate{}, printers.GenerateOptions{}) 6186 }, 6187 }, 6188 { 6189 name: "PodDisruptionBudget", 6190 printer: func() ([]metav1.TableRow, error) { 6191 return printPodDisruptionBudget(&policy.PodDisruptionBudget{}, printers.GenerateOptions{}) 6192 }, 6193 }, 6194 { 6195 name: "ReplicationController", 6196 printer: func() ([]metav1.TableRow, error) { 6197 return printReplicationController(&api.ReplicationController{}, printers.GenerateOptions{}) 6198 }, 6199 }, 6200 { 6201 name: "ReplicaSet", 6202 printer: func() ([]metav1.TableRow, error) { 6203 return printReplicaSet(&apps.ReplicaSet{}, printers.GenerateOptions{}) 6204 }, 6205 }, 6206 { 6207 name: "Job", 6208 printer: func() ([]metav1.TableRow, error) { 6209 return printJob(&batch.Job{}, printers.GenerateOptions{}) 6210 }, 6211 }, 6212 { 6213 name: "CronJob", 6214 printer: func() ([]metav1.TableRow, error) { 6215 return printCronJob(&batch.CronJob{}, printers.GenerateOptions{}) 6216 }, 6217 }, 6218 { 6219 name: "Service", 6220 printer: func() ([]metav1.TableRow, error) { 6221 return printService(&api.Service{}, printers.GenerateOptions{}) 6222 }, 6223 }, 6224 { 6225 name: "Ingress", 6226 printer: func() ([]metav1.TableRow, error) { 6227 return printIngress(&networking.Ingress{}, printers.GenerateOptions{}) 6228 }, 6229 }, 6230 { 6231 name: "IngressClass", 6232 printer: func() ([]metav1.TableRow, error) { 6233 return printIngressClass(&networking.IngressClass{}, printers.GenerateOptions{}) 6234 }, 6235 }, 6236 { 6237 name: "StatefulSet", 6238 printer: func() ([]metav1.TableRow, error) { 6239 return printStatefulSet(&apps.StatefulSet{}, printers.GenerateOptions{}) 6240 }, 6241 }, 6242 { 6243 name: "DaemonSet", 6244 printer: func() ([]metav1.TableRow, error) { 6245 return printDaemonSet(&apps.DaemonSet{}, printers.GenerateOptions{}) 6246 }, 6247 }, 6248 { 6249 name: "Endpoints", 6250 printer: func() ([]metav1.TableRow, error) { 6251 return printEndpoints(&api.Endpoints{}, printers.GenerateOptions{}) 6252 }, 6253 }, 6254 { 6255 name: "EndpointSlice", 6256 printer: func() ([]metav1.TableRow, error) { 6257 return printEndpointSlice(&discovery.EndpointSlice{}, printers.GenerateOptions{}) 6258 }, 6259 }, 6260 { 6261 name: "CSINode", 6262 printer: func() ([]metav1.TableRow, error) { 6263 return printCSINode(&storage.CSINode{}, printers.GenerateOptions{}) 6264 }, 6265 }, 6266 { 6267 name: "CSIDriver", 6268 printer: func() ([]metav1.TableRow, error) { 6269 return printCSIDriver(&storage.CSIDriver{}, printers.GenerateOptions{}) 6270 }, 6271 }, 6272 { 6273 name: "CSIStorageCapacity", 6274 printer: func() ([]metav1.TableRow, error) { 6275 return printCSIStorageCapacity(&storage.CSIStorageCapacity{}, printers.GenerateOptions{}) 6276 }, 6277 }, 6278 { 6279 name: "MutatingWebhookConfiguration", 6280 printer: func() ([]metav1.TableRow, error) { 6281 return printMutatingWebhook(&admissionregistration.MutatingWebhookConfiguration{}, printers.GenerateOptions{}) 6282 }, 6283 }, 6284 { 6285 name: "ValidatingWebhookConfiguration", 6286 printer: func() ([]metav1.TableRow, error) { 6287 return printValidatingWebhook(&admissionregistration.ValidatingWebhookConfiguration{}, printers.GenerateOptions{}) 6288 }, 6289 }, 6290 { 6291 name: "ValidatingAdmissionPolicy", 6292 printer: func() ([]metav1.TableRow, error) { 6293 return printValidatingAdmissionPolicy(&admissionregistration.ValidatingAdmissionPolicy{}, printers.GenerateOptions{}) 6294 }, 6295 }, 6296 { 6297 name: "ValidatingAdmissionPolicyBinding", 6298 printer: func() ([]metav1.TableRow, error) { 6299 return printValidatingAdmissionPolicyBinding(&admissionregistration.ValidatingAdmissionPolicyBinding{}, printers.GenerateOptions{}) 6300 }, 6301 }, 6302 { 6303 name: "Namespace", 6304 printer: func() ([]metav1.TableRow, error) { 6305 return printNamespace(&api.Namespace{}, printers.GenerateOptions{}) 6306 }, 6307 }, 6308 { 6309 name: "Secret", 6310 printer: func() ([]metav1.TableRow, error) { 6311 return printSecret(&api.Secret{}, printers.GenerateOptions{}) 6312 }, 6313 }, 6314 { 6315 name: "ServiceAccount", 6316 printer: func() ([]metav1.TableRow, error) { 6317 return printServiceAccount(&api.ServiceAccount{}, printers.GenerateOptions{}) 6318 }, 6319 }, 6320 { 6321 name: "Node", 6322 printer: func() ([]metav1.TableRow, error) { 6323 return printNode(&api.Node{}, printers.GenerateOptions{}) 6324 }, 6325 }, 6326 { 6327 name: "PersistentVolume", 6328 printer: func() ([]metav1.TableRow, error) { 6329 return printPersistentVolume(&api.PersistentVolume{}, printers.GenerateOptions{}) 6330 }, 6331 }, 6332 { 6333 name: "PersistentVolumeClaim", 6334 printer: func() ([]metav1.TableRow, error) { 6335 return printPersistentVolumeClaim(&api.PersistentVolumeClaim{}, printers.GenerateOptions{}) 6336 }, 6337 }, 6338 { 6339 name: "Event", 6340 printer: func() ([]metav1.TableRow, error) { 6341 return printEvent(&api.Event{}, printers.GenerateOptions{}) 6342 }, 6343 }, 6344 { 6345 name: "RoleBinding", 6346 printer: func() ([]metav1.TableRow, error) { 6347 return printRoleBinding(&rbac.RoleBinding{}, printers.GenerateOptions{}) 6348 }, 6349 }, 6350 { 6351 name: "ClusterRoleBinding", 6352 printer: func() ([]metav1.TableRow, error) { 6353 return printClusterRoleBinding(&rbac.ClusterRoleBinding{}, printers.GenerateOptions{}) 6354 }, 6355 }, 6356 { 6357 name: "CertificateSigningRequest", 6358 printer: func() ([]metav1.TableRow, error) { 6359 return printCertificateSigningRequest(&certificates.CertificateSigningRequest{}, printers.GenerateOptions{}) 6360 }, 6361 }, 6362 { 6363 name: "ComponentStatus", 6364 printer: func() ([]metav1.TableRow, error) { 6365 return printComponentStatus(&api.ComponentStatus{}, printers.GenerateOptions{}) 6366 }, 6367 }, 6368 { 6369 name: "Deployment", 6370 printer: func() ([]metav1.TableRow, error) { 6371 return printDeployment(&apps.Deployment{}, printers.GenerateOptions{}) 6372 }, 6373 }, 6374 { 6375 name: "HorizontalPodAutoscaler", 6376 printer: func() ([]metav1.TableRow, error) { 6377 return printHorizontalPodAutoscaler(&autoscaling.HorizontalPodAutoscaler{}, printers.GenerateOptions{}) 6378 }, 6379 }, 6380 { 6381 name: "ConfigMap", 6382 printer: func() ([]metav1.TableRow, error) { 6383 return printConfigMap(&api.ConfigMap{}, printers.GenerateOptions{}) 6384 }, 6385 }, 6386 { 6387 name: "NetworkPolicy", 6388 printer: func() ([]metav1.TableRow, error) { 6389 return printNetworkPolicy(&networking.NetworkPolicy{}, printers.GenerateOptions{}) 6390 }, 6391 }, 6392 { 6393 name: "StorageClass", 6394 printer: func() ([]metav1.TableRow, error) { 6395 return printStorageClass(&storage.StorageClass{}, printers.GenerateOptions{}) 6396 }, 6397 }, 6398 { 6399 name: "Lease", 6400 printer: func() ([]metav1.TableRow, error) { 6401 return printLease(&coordination.Lease{}, printers.GenerateOptions{}) 6402 }, 6403 }, 6404 { 6405 name: "ControllerRevision", 6406 printer: func() ([]metav1.TableRow, error) { 6407 return printControllerRevision(&apps.ControllerRevision{}, printers.GenerateOptions{}) 6408 }, 6409 }, 6410 { 6411 name: "ResourceQuota", 6412 printer: func() ([]metav1.TableRow, error) { 6413 return printResourceQuota(&api.ResourceQuota{}, printers.GenerateOptions{}) 6414 }, 6415 }, 6416 { 6417 name: "PriorityClass", 6418 printer: func() ([]metav1.TableRow, error) { 6419 return printPriorityClass(&scheduling.PriorityClass{}, printers.GenerateOptions{}) 6420 }, 6421 }, 6422 { 6423 name: "RuntimeClass", 6424 printer: func() ([]metav1.TableRow, error) { 6425 return printRuntimeClass(&nodeapi.RuntimeClass{}, printers.GenerateOptions{}) 6426 }, 6427 }, 6428 { 6429 name: "VolumeAttachment", 6430 printer: func() ([]metav1.TableRow, error) { 6431 return printVolumeAttachment(&storage.VolumeAttachment{}, printers.GenerateOptions{}) 6432 }, 6433 }, 6434 { 6435 name: "FlowSchema", 6436 printer: func() ([]metav1.TableRow, error) { 6437 return printFlowSchema(&flowcontrol.FlowSchema{}, printers.GenerateOptions{}) 6438 }, 6439 }, 6440 { 6441 name: "StorageVersion", 6442 printer: func() ([]metav1.TableRow, error) { 6443 return printStorageVersion(&apiserverinternal.StorageVersion{}, printers.GenerateOptions{}) 6444 }, 6445 }, 6446 { 6447 name: "PriorityLevelConfiguration", 6448 printer: func() ([]metav1.TableRow, error) { 6449 return printPriorityLevelConfiguration(&flowcontrol.PriorityLevelConfiguration{}, printers.GenerateOptions{}) 6450 }, 6451 }, 6452 { 6453 name: "Scale", 6454 printer: func() ([]metav1.TableRow, error) { 6455 return printScale(&autoscaling.Scale{}, printers.GenerateOptions{}) 6456 }, 6457 }, 6458 { 6459 name: "Status", 6460 printer: func() ([]metav1.TableRow, error) { 6461 return printStatus(&metav1.Status{}, printers.GenerateOptions{}) 6462 }, 6463 }, 6464 } 6465 6466 for _, test := range tests { 6467 t.Run(test.name, func(t *testing.T) { 6468 rows, err := test.printer() 6469 if err != nil { 6470 t.Fatalf("expected no error, but got: %#v", err) 6471 } 6472 if len(rows) <= 0 { 6473 t.Fatalf("expected to have at least one TableRow, but got: %d", len(rows)) 6474 } 6475 6476 func() { 6477 defer func() { 6478 if err := recover(); err != nil { 6479 // Same as stdlib http server code. Manually allocate stack 6480 // trace buffer size to prevent excessively large logs 6481 const size = 64 << 10 6482 buf := make([]byte, size) 6483 buf = buf[:runtime.Stack(buf, false)] 6484 err = fmt.Errorf("%q stack:\n%s", err, buf) 6485 6486 t.Errorf("Expected no panic, but got: %v", err) 6487 } 6488 }() 6489 6490 // should not panic 6491 rows[0].DeepCopy() 6492 }() 6493 6494 }) 6495 } 6496 } 6497 6498 func TestPrintIPAddress(t *testing.T) { 6499 ip := networking.IPAddress{ 6500 ObjectMeta: metav1.ObjectMeta{ 6501 Name: "192.168.2.2", 6502 CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, 6503 }, 6504 Spec: networking.IPAddressSpec{ 6505 ParentRef: &networking.ParentReference{ 6506 Group: "mygroup", 6507 Resource: "myresource", 6508 Namespace: "mynamespace", 6509 Name: "myname", 6510 }, 6511 }, 6512 } 6513 // Columns: Name, ParentRef, Age 6514 expected := []metav1.TableRow{{Cells: []interface{}{"192.168.2.2", "myresource.mygroup/mynamespace/myname", "10y"}}} 6515 6516 rows, err := printIPAddress(&ip, printers.GenerateOptions{}) 6517 if err != nil { 6518 t.Fatalf("Error generating table rows for IPAddress: %#v", err) 6519 } 6520 rows[0].Object.Object = nil 6521 if !reflect.DeepEqual(expected, rows) { 6522 t.Errorf("mismatch: %s", cmp.Diff(expected, rows)) 6523 } 6524 } 6525 6526 func TestPrintIPAddressList(t *testing.T) { 6527 ipList := networking.IPAddressList{ 6528 Items: []networking.IPAddress{ 6529 { 6530 ObjectMeta: metav1.ObjectMeta{ 6531 Name: "192.168.2.2", 6532 CreationTimestamp: metav1.Time{}, 6533 }, 6534 Spec: networking.IPAddressSpec{ 6535 ParentRef: &networking.ParentReference{ 6536 Group: "mygroup", 6537 Resource: "myresource", 6538 Namespace: "mynamespace", 6539 Name: "myname", 6540 }, 6541 }, 6542 }, { 6543 ObjectMeta: metav1.ObjectMeta{ 6544 Name: "2001:db8::2", 6545 CreationTimestamp: metav1.Time{}, 6546 }, 6547 Spec: networking.IPAddressSpec{ 6548 ParentRef: &networking.ParentReference{ 6549 Group: "mygroup2", 6550 Resource: "myresource2", 6551 Namespace: "mynamespace2", 6552 Name: "myname2", 6553 }, 6554 }, 6555 }, 6556 }, 6557 } 6558 // Columns: Name, ParentRef, Age 6559 expected := []metav1.TableRow{ 6560 {Cells: []interface{}{"192.168.2.2", "myresource.mygroup/mynamespace/myname", "<unknown>"}}, 6561 {Cells: []interface{}{"2001:db8::2", "myresource2.mygroup2/mynamespace2/myname2", "<unknown>"}}, 6562 } 6563 6564 rows, err := printIPAddressList(&ipList, printers.GenerateOptions{}) 6565 if err != nil { 6566 t.Fatalf("Error generating table rows for IPAddress: %#v", err) 6567 } 6568 for i := range rows { 6569 rows[i].Object.Object = nil 6570 6571 } 6572 if !reflect.DeepEqual(expected, rows) { 6573 t.Errorf("mismatch: %s", cmp.Diff(expected, rows)) 6574 } 6575 6576 } 6577 6578 func TestPrintServiceCIDR(t *testing.T) { 6579 ipv4CIDR := "10.1.0.0/16" 6580 ipv6CIDR := "fd00:1:1::/64" 6581 6582 tests := []struct { 6583 ccc networking.ServiceCIDR 6584 options printers.GenerateOptions 6585 expected []metav1.TableRow 6586 }{ 6587 { 6588 // Test name, IPv4 only. 6589 ccc: networking.ServiceCIDR{ 6590 ObjectMeta: metav1.ObjectMeta{Name: "test1"}, 6591 Spec: networking.ServiceCIDRSpec{ 6592 CIDRs: []string{ipv4CIDR}, 6593 }, 6594 }, 6595 options: printers.GenerateOptions{}, 6596 // Columns: Name, IPv4, IPv6, Age. 6597 expected: []metav1.TableRow{{Cells: []interface{}{"test1", ipv4CIDR, "<unknown>"}}}, 6598 }, 6599 { 6600 // Test name, IPv6 only. 6601 ccc: networking.ServiceCIDR{ 6602 ObjectMeta: metav1.ObjectMeta{Name: "test5"}, 6603 Spec: networking.ServiceCIDRSpec{ 6604 CIDRs: []string{ipv6CIDR}, 6605 }, 6606 }, 6607 options: printers.GenerateOptions{}, 6608 // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age 6609 expected: []metav1.TableRow{{Cells: []interface{}{"test5", ipv6CIDR, "<unknown>"}}}, 6610 }, 6611 { 6612 // Test name, DualStack. 6613 ccc: networking.ServiceCIDR{ 6614 ObjectMeta: metav1.ObjectMeta{Name: "test9"}, 6615 Spec: networking.ServiceCIDRSpec{ 6616 CIDRs: []string{ipv4CIDR, ipv6CIDR}, 6617 }, 6618 }, 6619 options: printers.GenerateOptions{}, 6620 // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age. 6621 expected: []metav1.TableRow{{Cells: []interface{}{"test9", ipv4CIDR + "," + ipv6CIDR, "<unknown>"}}}, 6622 }, 6623 } 6624 6625 for i, test := range tests { 6626 rows, err := printServiceCIDR(&test.ccc, test.options) 6627 if err != nil { 6628 t.Fatal(err) 6629 } 6630 for i := range rows { 6631 rows[i].Object.Object = nil 6632 } 6633 if !reflect.DeepEqual(test.expected, rows) { 6634 t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) 6635 } 6636 } 6637 } 6638 6639 func TestPrintServiceCIDRList(t *testing.T) { 6640 cccList := networking.ServiceCIDRList{ 6641 Items: []networking.ServiceCIDR{ 6642 { 6643 ObjectMeta: metav1.ObjectMeta{Name: "ccc1"}, 6644 Spec: networking.ServiceCIDRSpec{ 6645 CIDRs: []string{"10.1.0.0/16", "fd00:1:1::/64"}, 6646 }, 6647 }, 6648 { 6649 ObjectMeta: metav1.ObjectMeta{Name: "ccc2"}, 6650 Spec: networking.ServiceCIDRSpec{ 6651 CIDRs: []string{"10.2.0.0/16", "fd00:2:1::/64"}, 6652 }, 6653 }, 6654 }, 6655 } 6656 6657 tests := []struct { 6658 options printers.GenerateOptions 6659 expected []metav1.TableRow 6660 }{ 6661 { 6662 // Test name, DualStack with node selector, wide. 6663 options: printers.GenerateOptions{Wide: false}, 6664 expected: []metav1.TableRow{ 6665 // Columns: Name, IPv4, IPv6, Age. 6666 {Cells: []interface{}{"ccc1", "10.1.0.0/16,fd00:1:1::/64", "<unknown>"}}, 6667 {Cells: []interface{}{"ccc2", "10.2.0.0/16,fd00:2:1::/64", "<unknown>"}}, 6668 }, 6669 }, 6670 { 6671 // Test name, DualStack with node selector, wide. 6672 options: printers.GenerateOptions{Wide: true}, 6673 expected: []metav1.TableRow{ 6674 // Columns: Name, CIDRs, Age. 6675 {Cells: []interface{}{"ccc1", "10.1.0.0/16,fd00:1:1::/64", "<unknown>"}}, 6676 {Cells: []interface{}{"ccc2", "10.2.0.0/16,fd00:2:1::/64", "<unknown>"}}, 6677 }, 6678 }, 6679 } 6680 6681 for _, test := range tests { 6682 rows, err := printServiceCIDRList(&cccList, test.options) 6683 if err != nil { 6684 t.Fatalf("Error printing service list: %#v", err) 6685 } 6686 for i := range rows { 6687 rows[i].Object.Object = nil 6688 } 6689 if !reflect.DeepEqual(test.expected, rows) { 6690 t.Errorf("mismatch: %s", cmp.Diff(test.expected, rows)) 6691 } 6692 } 6693 }