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