/* Copyright 2014 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package internalversion import ( "fmt" "math" "reflect" "runtime" "testing" "time" "github.com/google/go-cmp/cmp" apiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/util/certificate/csr" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/apiserverinternal" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/certificates" "k8s.io/kubernetes/pkg/apis/coordination" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/discovery" "k8s.io/kubernetes/pkg/apis/flowcontrol" "k8s.io/kubernetes/pkg/apis/networking" nodeapi "k8s.io/kubernetes/pkg/apis/node" "k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/scheduling" "k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/apis/storagemigration" "k8s.io/kubernetes/pkg/printers" utilpointer "k8s.io/utils/pointer" ) var containerRestartPolicyAlways = api.ContainerRestartPolicyAlways func TestFormatResourceName(t *testing.T) { tests := []struct { kind schema.GroupKind name string want string }{ {schema.GroupKind{}, "", ""}, {schema.GroupKind{}, "name", "name"}, {schema.GroupKind{Kind: "Kind"}, "", "kind/"}, // should not happen in practice {schema.GroupKind{Kind: "Kind"}, "name", "kind/name"}, {schema.GroupKind{Group: "group", Kind: "Kind"}, "name", "kind.group/name"}, } for _, tt := range tests { if got := formatResourceName(tt.kind, tt.name, true); got != tt.want { t.Errorf("formatResourceName(%q, %q) = %q, want %q", tt.kind, tt.name, got, tt.want) } } } type TestPrintHandler struct { numCalls int } func (t *TestPrintHandler) TableHandler(columnDefinitions []metav1.TableColumnDefinition, printFunc interface{}) error { t.numCalls++ return nil } func (t *TestPrintHandler) getNumCalls() int { return t.numCalls } func TestAllHandlers(t *testing.T) { h := &TestPrintHandler{numCalls: 0} AddHandlers(h) if h.getNumCalls() == 0 { t.Error("TableHandler not called in AddHandlers") } } func TestPrintEvent(t *testing.T) { tests := []struct { event api.Event options printers.GenerateOptions expected []metav1.TableRow }{ // Basic event; no generate options { event: api.Event{ Source: api.EventSource{Component: "kubelet"}, InvolvedObject: api.ObjectReference{ Kind: "Pod", Name: "Pod Name", FieldPath: "spec.containers{foo}", }, Reason: "Event Reason", Message: "Message Data", FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)}, LastTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}, Count: 6, Type: api.EventTypeNormal, ObjectMeta: metav1.ObjectMeta{Name: "event1"}, }, options: printers.GenerateOptions{}, // Columns: Last Seen, Type, Reason, Object, Message expected: []metav1.TableRow{{Cells: []interface{}{"2d", "Normal", "Event Reason", "pod/Pod Name", "Message Data"}}}, }, // Basic event; generate options=Wide { event: api.Event{ Source: api.EventSource{ Component: "kubelet", Host: "Node1", }, InvolvedObject: api.ObjectReference{ Kind: "Deployment", Name: "Deployment Name", FieldPath: "spec.containers{foo}", }, Reason: "Event Reason", Message: "Message Data", FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)}, LastTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}, Count: 6, Type: api.EventTypeWarning, ObjectMeta: metav1.ObjectMeta{Name: "event2"}, }, options: printers.GenerateOptions{Wide: true}, // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name expected: []metav1.TableRow{{Cells: []interface{}{"2d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(6), "event2"}}}, }, // Basic event, w/o FirstTimestamp set { event: api.Event{ Source: api.EventSource{ Component: "kubelet", Host: "Node1", }, InvolvedObject: api.ObjectReference{ Kind: "Deployment", Name: "Deployment Name", FieldPath: "spec.containers{foo}", }, Reason: "Event Reason", Message: "Message Data", EventTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)}, LastTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)}, Count: 1, Type: api.EventTypeWarning, ObjectMeta: metav1.ObjectMeta{Name: "event3"}, }, options: printers.GenerateOptions{Wide: true}, // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name expected: []metav1.TableRow{{Cells: []interface{}{"3d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(1), "event3"}}}, }, // Basic event, w/o LastTimestamp set { event: api.Event{ Source: api.EventSource{ Component: "kubelet", Host: "Node1", }, InvolvedObject: api.ObjectReference{ Kind: "Deployment", Name: "Deployment Name", FieldPath: "spec.containers{foo}", }, Reason: "Event Reason", Message: "Message Data", EventTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)}, FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)}, Count: 1, Type: api.EventTypeWarning, ObjectMeta: metav1.ObjectMeta{Name: "event4"}, }, options: printers.GenerateOptions{Wide: true}, // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name expected: []metav1.TableRow{{Cells: []interface{}{"3d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(1), "event4"}}}, }, // Basic event, w/o FirstTimestamp and LastTimestamp set { event: api.Event{ Source: api.EventSource{ Component: "kubelet", Host: "Node1", }, InvolvedObject: api.ObjectReference{ Kind: "Deployment", Name: "Deployment Name", FieldPath: "spec.containers{foo}", }, Reason: "Event Reason", Message: "Message Data", EventTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)}, Count: 1, Type: api.EventTypeWarning, ObjectMeta: metav1.ObjectMeta{Name: "event5"}, }, options: printers.GenerateOptions{Wide: true}, // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name expected: []metav1.TableRow{{Cells: []interface{}{"3d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(1), "event5"}}}, }, // Basic event serie, w/o FirstTimestamp, LastTimestamp and Count set { event: api.Event{ Source: api.EventSource{ Component: "kubelet", Host: "Node1", }, InvolvedObject: api.ObjectReference{ Kind: "Deployment", Name: "Deployment Name", FieldPath: "spec.containers{foo}", }, Series: &api.EventSeries{ Count: 2, LastObservedTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -2)}, }, Reason: "Event Reason", Message: "Message Data", EventTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)}, Type: api.EventTypeWarning, ObjectMeta: metav1.ObjectMeta{Name: "event6"}, }, options: printers.GenerateOptions{Wide: true}, // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name expected: []metav1.TableRow{{Cells: []interface{}{"2d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(2), "event6"}}}, }, // Singleton event, w/o FirstTimestamp, LastTimestamp and Count set { event: api.Event{ Source: api.EventSource{ Component: "kubelet", Host: "Node1", }, InvolvedObject: api.ObjectReference{ Kind: "Deployment", Name: "Deployment Name", FieldPath: "spec.containers{foo}", }, Reason: "Event Reason", Message: "Message Data", EventTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)}, Type: api.EventTypeWarning, ObjectMeta: metav1.ObjectMeta{Name: "event7"}, }, options: printers.GenerateOptions{Wide: true}, // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name expected: []metav1.TableRow{{Cells: []interface{}{"3d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(1), "event7"}}}, }, // Basic event, with empty Source; generate options=Wide { event: api.Event{ ReportingController: "kubelet", ReportingInstance: "test", InvolvedObject: api.ObjectReference{ Kind: "Deployment", Name: "Deployment Name", FieldPath: "spec.containers{foo}", }, Reason: "Event Reason", Message: "Message Data", FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)}, LastTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}, Count: 6, Type: api.EventTypeWarning, ObjectMeta: metav1.ObjectMeta{Name: "event2"}, }, options: printers.GenerateOptions{Wide: true}, // Columns: Last Seen, Type, Reason, Object, Subobject, Source, Message, First Seen, Count, Name expected: []metav1.TableRow{{Cells: []interface{}{"2d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, test", "Message Data", "3d", int64(6), "event2"}}}, }, } for i, test := range tests { rows, err := printEvent(&test.event, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintEventsResultSorted(t *testing.T) { eventList := api.EventList{ Items: []api.Event{ { Source: api.EventSource{Component: "kubelet"}, Message: "Item 1", FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), LastTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), Count: 1, Type: api.EventTypeNormal, }, { Source: api.EventSource{Component: "scheduler"}, Message: "Item 2", FirstTimestamp: metav1.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)), LastTimestamp: metav1.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)), Count: 1, Type: api.EventTypeNormal, }, { Source: api.EventSource{Component: "kubelet"}, Message: "Item 3", FirstTimestamp: metav1.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)), LastTimestamp: metav1.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)), Count: 1, Type: api.EventTypeNormal, }, }, } rows, err := printEventList(&eventList, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } if len(rows) != 3 { t.Errorf("Generate Event List: Wrong number of table rows returned. Expected 3, got (%d)", len(rows)) } // Verify the watch event dates are in order. firstRow := rows[0] message1 := firstRow.Cells[4] if message1.(string) != "Item 1" { t.Errorf("Wrong event ordering: expecting (Item 1), got (%s)", message1) } secondRow := rows[1] message2 := secondRow.Cells[4] if message2 != "Item 2" { t.Errorf("Wrong event ordering: expecting (Item 2), got (%s)", message2) } thirdRow := rows[2] message3 := thirdRow.Cells[4] if message3 != "Item 3" { t.Errorf("Wrong event ordering: expecting (Item 3), got (%s)", message3) } } func TestPrintNamespace(t *testing.T) { tests := []struct { namespace api.Namespace expected []metav1.TableRow }{ // Basic namespace with status and age. { namespace: api.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: "namespace1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Status: api.NamespaceStatus{ Phase: "FooStatus", }, }, // Columns: Name, Status, Age expected: []metav1.TableRow{{Cells: []interface{}{"namespace1", "FooStatus", "0s"}}}, }, // Basic namespace without status or age. { namespace: api.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: "namespace2", }, }, // Columns: Name, Status, Age expected: []metav1.TableRow{{Cells: []interface{}{"namespace2", "", ""}}}, }, } for i, test := range tests { rows, err := printNamespace(&test.namespace, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintSecret(t *testing.T) { tests := []struct { secret api.Secret expected []metav1.TableRow }{ // Basic namespace with type, data, and age. { secret: api.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "secret1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Type: "kubernetes.io/service-account-token", Data: map[string][]byte{ "token": []byte("secret data"), }, }, // Columns: Name, Type, Data, Age expected: []metav1.TableRow{{Cells: []interface{}{"secret1", "kubernetes.io/service-account-token", int64(1), "0s"}}}, }, // Basic namespace with type and age; no data. { secret: api.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "secret1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Type: "kubernetes.io/service-account-token", }, // Columns: Name, Type, Data, Age expected: []metav1.TableRow{{Cells: []interface{}{"secret1", "kubernetes.io/service-account-token", int64(0), "0s"}}}, }, } for i, test := range tests { rows, err := printSecret(&test.secret, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintServiceAccount(t *testing.T) { tests := []struct { serviceAccount api.ServiceAccount expected []metav1.TableRow }{ // Basic service account without secrets { serviceAccount: api.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: "sa1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Secrets: []api.ObjectReference{}, }, // Columns: Name, (Num) Secrets, Age expected: []metav1.TableRow{{Cells: []interface{}{"sa1", int64(0), "0s"}}}, }, // Basic service account with two secrets. { serviceAccount: api.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: "sa1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Secrets: []api.ObjectReference{ {Name: "Secret1"}, {Name: "Secret2"}, }, }, // Columns: Name, (Num) Secrets, Age expected: []metav1.TableRow{{Cells: []interface{}{"sa1", int64(2), "0s"}}}, }, } for i, test := range tests { rows, err := printServiceAccount(&test.serviceAccount, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintNodeStatus(t *testing.T) { table := []struct { node api.Node expected []metav1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionTrue}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"foo1", "Ready", "", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, Spec: api.NodeSpec{Unschedulable: true}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionTrue}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"foo2", "Ready,SchedulingDisabled", "", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo3"}, Status: api.NodeStatus{Conditions: []api.NodeCondition{ {Type: api.NodeReady, Status: api.ConditionTrue}, {Type: api.NodeReady, Status: api.ConditionTrue}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"foo3", "Ready", "", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo4"}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionFalse}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"foo4", "NotReady", "", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo5"}, Spec: api.NodeSpec{Unschedulable: true}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionFalse}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"foo5", "NotReady,SchedulingDisabled", "", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo6"}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: "InvalidValue", Status: api.ConditionTrue}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"foo6", "Unknown", "", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo7"}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"foo7", "Unknown", "", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo8"}, Spec: api.NodeSpec{Unschedulable: true}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: "InvalidValue", Status: api.ConditionTrue}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"foo8", "Unknown,SchedulingDisabled", "", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo9"}, Spec: api.NodeSpec{Unschedulable: true}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"foo9", "Unknown,SchedulingDisabled", "", "", ""}}}, }, } for i, test := range table { rows, err := printNode(&test.node, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error generating table rows for Node: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintNodeRole(t *testing.T) { table := []struct { node api.Node expected []metav1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo9"}, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"foo9", "Unknown", "", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "foo10", Labels: map[string]string{"node-role.kubernetes.io/master": "", "node-role.kubernetes.io/control-plane": "", "node-role.kubernetes.io/proxy": "", "kubernetes.io/role": "node"}, }, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"foo10", "Unknown", "control-plane,master,node,proxy", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "foo11", Labels: map[string]string{"kubernetes.io/role": "node"}, }, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"foo11", "Unknown", "node", "", ""}}}, }, } for i, test := range table { rows, err := printNode(&test.node, printers.GenerateOptions{}) if err != nil { t.Fatalf("An error occurred generating table rows for Node: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintNodeOSImage(t *testing.T) { table := []struct { node api.Node expected []metav1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, Status: api.NodeStatus{ NodeInfo: api.NodeSystemInfo{OSImage: "fake-os-image"}, Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, }, }, // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion expected: []metav1.TableRow{ { Cells: []interface{}{"foo1", "Unknown", "", "", "", "", "1.1.1.1", "fake-os-image", "", ""}, }, }, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, Status: api.NodeStatus{ NodeInfo: api.NodeSystemInfo{KernelVersion: "fake-kernel-version"}, Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, }, }, // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion expected: []metav1.TableRow{ { Cells: []interface{}{"foo2", "Unknown", "", "", "", "", "1.1.1.1", "", "fake-kernel-version", ""}, }, }, }, } for i, test := range table { rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) if err != nil { t.Fatalf("An error occurred generating table for Node: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintNodeKernelVersion(t *testing.T) { table := []struct { node api.Node expected []metav1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, Status: api.NodeStatus{ NodeInfo: api.NodeSystemInfo{KernelVersion: "fake-kernel-version"}, Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, }, }, // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion expected: []metav1.TableRow{ { Cells: []interface{}{"foo1", "Unknown", "", "", "", "", "1.1.1.1", "", "fake-kernel-version", ""}, }, }, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, Status: api.NodeStatus{ NodeInfo: api.NodeSystemInfo{OSImage: "fake-os-image"}, Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, }, }, // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion expected: []metav1.TableRow{ { Cells: []interface{}{"foo2", "Unknown", "", "", "", "", "1.1.1.1", "fake-os-image", "", ""}, }, }, }, } for i, test := range table { rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) if err != nil { t.Fatalf("An error occurred generating table rows Node: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintNodeContainerRuntimeVersion(t *testing.T) { table := []struct { node api.Node expected []metav1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, Status: api.NodeStatus{ NodeInfo: api.NodeSystemInfo{ContainerRuntimeVersion: "foo://1.2.3"}, Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, }, }, // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion expected: []metav1.TableRow{ { Cells: []interface{}{"foo1", "Unknown", "", "", "", "", "1.1.1.1", "", "", "foo://1.2.3"}, }, }, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, Status: api.NodeStatus{ NodeInfo: api.NodeSystemInfo{}, Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, }, }, // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion expected: []metav1.TableRow{ { Cells: []interface{}{"foo2", "Unknown", "", "", "", "", "1.1.1.1", "", "", ""}, }, }, }, } for i, test := range table { rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) if err != nil { t.Fatalf("An error occurred generating table rows Node: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintNodeName(t *testing.T) { table := []struct { node api.Node expected []metav1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "127.0.0.1"}, Status: api.NodeStatus{}, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"127.0.0.1", "Unknown", "", "", ""}}}}, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: ""}, Status: api.NodeStatus{}, }, // Columns: Name, Status, Roles, Age, KubeletVersion expected: []metav1.TableRow{{Cells: []interface{}{"", "Unknown", "", "", ""}}}, }, } for i, test := range table { rows, err := printNode(&test.node, printers.GenerateOptions{}) if err != nil { t.Fatalf("An error occurred generating table rows Node: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintNodeExternalIP(t *testing.T) { table := []struct { node api.Node expected []metav1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, Status: api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion expected: []metav1.TableRow{ { Cells: []interface{}{"foo1", "Unknown", "", "", "", "", "1.1.1.1", "", "", ""}, }, }, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, Status: api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeInternalIP, Address: "1.1.1.1"}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion expected: []metav1.TableRow{ { Cells: []interface{}{"foo2", "Unknown", "", "", "", "1.1.1.1", "", "", "", ""}, }, }, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo3"}, Status: api.NodeStatus{Addresses: []api.NodeAddress{ {Type: api.NodeExternalIP, Address: "2.2.2.2"}, {Type: api.NodeInternalIP, Address: "3.3.3.3"}, {Type: api.NodeExternalIP, Address: "4.4.4.4"}, }}, }, expected: []metav1.TableRow{ { Cells: []interface{}{"foo3", "Unknown", "", "", "", "3.3.3.3", "2.2.2.2", "", "", ""}, }, }, }, } for i, test := range table { rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) if err != nil { t.Fatalf("An error occurred generating table rows Node: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintNodeInternalIP(t *testing.T) { table := []struct { node api.Node expected []metav1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, Status: api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeInternalIP, Address: "1.1.1.1"}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion expected: []metav1.TableRow{ { Cells: []interface{}{"foo1", "Unknown", "", "", "", "1.1.1.1", "", "", "", ""}, }, }, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, Status: api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}}, }, // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion expected: []metav1.TableRow{ { Cells: []interface{}{"foo2", "Unknown", "", "", "", "", "1.1.1.1", "", "", ""}, }, }, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo3"}, Status: api.NodeStatus{Addresses: []api.NodeAddress{ {Type: api.NodeInternalIP, Address: "2.2.2.2"}, {Type: api.NodeExternalIP, Address: "3.3.3.3"}, {Type: api.NodeInternalIP, Address: "4.4.4.4"}, }}, }, // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion expected: []metav1.TableRow{ { Cells: []interface{}{"foo3", "Unknown", "", "", "", "2.2.2.2", "3.3.3.3", "", "", ""}, }, }, }, } for i, test := range table { rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) if err != nil { t.Fatalf("An error occurred generating table rows Node: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintIngress(t *testing.T) { ingress := networking.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, Spec: networking.IngressSpec{ IngressClassName: utilpointer.StringPtr("foo"), DefaultBackend: &networking.IngressBackend{ Service: &networking.IngressServiceBackend{ Name: "default-backend", Port: networking.ServiceBackendPort{ Name: "default-backend", Number: 80, }, }, }, }, Status: networking.IngressStatus{ LoadBalancer: networking.IngressLoadBalancerStatus{ Ingress: []networking.IngressLoadBalancerIngress{ { IP: "2.3.4.5", Hostname: "localhost.localdomain", }, }, }, }, } // Columns: Name, Hosts, Address, Ports, Age expected := []metav1.TableRow{{Cells: []interface{}{"test1", "foo", "*", "2.3.4.5", "80", "10y"}}} rows, err := printIngress(&ingress, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error generating table rows for Ingress: %#v", err) } rows[0].Object.Object = nil if !reflect.DeepEqual(expected, rows) { t.Errorf("mismatch: %s", cmp.Diff(expected, rows)) } } func TestPrintIngressClass(t *testing.T) { testCases := []struct { name string ingressClass *networking.IngressClass expected []metav1.TableRow }{{ name: "example with params", ingressClass: &networking.IngressClass{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, Spec: networking.IngressClassSpec{ Controller: "example.com/controller", Parameters: &networking.IngressClassParametersReference{Kind: "customgroup", Name: "example"}, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test1", "example.com/controller", "customgroup/example", "10y"}}}, }, { name: "example with params + API Group", ingressClass: &networking.IngressClass{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, Spec: networking.IngressClassSpec{ Controller: "example.com/controller", Parameters: &networking.IngressClassParametersReference{ APIGroup: utilpointer.StringPtr("example.com"), Kind: "customgroup", Name: "example", }, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test1", "example.com/controller", "customgroup.example.com/example", "10y"}}}, }, { name: "example without params", ingressClass: &networking.IngressClass{ ObjectMeta: metav1.ObjectMeta{ Name: "test2", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-11, 0, 0)}, }, Spec: networking.IngressClassSpec{ Controller: "example.com/controller2", }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test2", "example.com/controller2", "", "11y"}}}, }} for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { rows, err := printIngressClass(testCase.ingressClass, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error generating table rows for Ingress: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(testCase.expected, rows) { t.Errorf("mismatch: %s", cmp.Diff(testCase.expected, rows)) } }) } } func TestPrintServiceLoadBalancer(t *testing.T) { tests := []struct { service api.Service options printers.GenerateOptions expected []metav1.TableRow }{ // Test load balancer service with multiple external IP's { service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "service1"}, Spec: api.ServiceSpec{ ClusterIPs: []string{"1.2.3.4"}, Type: "LoadBalancer", Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}}, }, Status: api.ServiceStatus{ LoadBalancer: api.LoadBalancerStatus{ Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}}}, }, }, options: printers.GenerateOptions{}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age expected: []metav1.TableRow{{Cells: []interface{}{"service1", "LoadBalancer", "1.2.3.4", "2.3.4.5,3.4.5.6", "80/TCP", ""}}}, }, // Test load balancer service with pending external IP. { service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "service2"}, Spec: api.ServiceSpec{ ClusterIPs: []string{"1.3.4.5"}, Type: "LoadBalancer", Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}, {Port: 7777, Protocol: "SCTP"}}, }, }, options: printers.GenerateOptions{}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age expected: []metav1.TableRow{{Cells: []interface{}{"service2", "LoadBalancer", "1.3.4.5", "", "80/TCP,8090/UDP,8000/TCP,7777/SCTP", ""}}}, }, // Test load balancer service with multiple ports. { service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "service3"}, Spec: api.ServiceSpec{ ClusterIPs: []string{"1.4.5.6"}, Type: "LoadBalancer", Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}}, }, Status: api.ServiceStatus{ LoadBalancer: api.LoadBalancerStatus{ Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}}}, }, }, options: printers.GenerateOptions{}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age expected: []metav1.TableRow{{Cells: []interface{}{"service3", "LoadBalancer", "1.4.5.6", "2.3.4.5", "80/TCP,8090/UDP,8000/TCP", ""}}}, }, // Long external IP's list gets elided. { service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "service4"}, Spec: api.ServiceSpec{ ClusterIPs: []string{"1.5.6.7"}, Type: "LoadBalancer", Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}}, }, Status: api.ServiceStatus{ LoadBalancer: api.LoadBalancerStatus{ Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}, {IP: "5.6.7.8", Hostname: "host5678"}}}, }, }, options: printers.GenerateOptions{}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age expected: []metav1.TableRow{{Cells: []interface{}{"service4", "LoadBalancer", "1.5.6.7", "2.3.4.5,3.4.5...", "80/TCP,8090/UDP,8000/TCP", ""}}}, }, // Generate options: Wide, includes selectors. { service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "service4"}, Spec: api.ServiceSpec{ ClusterIPs: []string{"1.5.6.7"}, Type: "LoadBalancer", Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}}, Selector: map[string]string{"foo": "bar"}, }, Status: api.ServiceStatus{ LoadBalancer: api.LoadBalancerStatus{ Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}, {IP: "5.6.7.8", Hostname: "host5678"}}}, }, }, options: printers.GenerateOptions{Wide: true}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 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", "", "foo=bar"}}}, }, } for i, test := range tests { rows, err := printService(&test.service, test.options) if err != nil { t.Fatalf("Error printing table rows for Service: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintPod(t *testing.T) { tests := []struct { pod api.Pod expect []metav1.TableRow }{ { // Test name, num of containers, restarts, container ready status api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test1"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "podPhase", ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {RestartCount: 3}, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", "6", ""}}}, }, { // Test container error overwrites pod phase api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test2"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "podPhase", ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {State: api.ContainerState{Waiting: &api.ContainerStateWaiting{Reason: "ContainerWaitingReason"}}, RestartCount: 3}, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test2", "1/2", "ContainerWaitingReason", "6", ""}}}, }, { // Test the same as the above but with Terminated state and the first container overwrites the rest api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test3"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "podPhase", ContainerStatuses: []api.ContainerStatus{ {State: api.ContainerState{Waiting: &api.ContainerStateWaiting{Reason: "ContainerWaitingReason"}}, RestartCount: 3}, {State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "ContainerTerminatedReason"}}, RestartCount: 3}, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test3", "0/2", "ContainerWaitingReason", "6", ""}}}, }, { // Test ready is not enough for reporting running api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test4"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "podPhase", ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {Ready: true, RestartCount: 3}, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test4", "1/2", "podPhase", "6", ""}}}, }, { // Test ready is not enough for reporting running api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test5"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Reason: "podReason", Phase: "podPhase", ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {Ready: true, RestartCount: 3}, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test5", "1/2", "podReason", "6", ""}}}, }, { // Test pod has 2 containers, one is running and the other is completed, w/o ready condition api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test6"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "Running", Reason: "", ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Completed", ExitCode: 0}}}, {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test6", "1/2", "NotReady", "6", ""}}}, }, { // Test pod has 2 containers, one is running and the other is completed, with ready condition api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test6"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "Running", Reason: "", ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Completed", ExitCode: 0}}}, {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, }, Conditions: []api.PodCondition{ {Type: api.PodReady, Status: api.ConditionTrue, LastProbeTime: metav1.Time{Time: time.Now()}}, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test6", "1/2", "Running", "6", ""}}}, }, { // Test pod has 1 init container restarting and 1 container not running api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test7"}, Spec: api.PodSpec{InitContainers: make([]api.Container, 1), Containers: make([]api.Container, 1)}, Status: api.PodStatus{ Phase: "podPhase", InitContainerStatuses: []api.ContainerStatus{ { Ready: false, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, }, }, ContainerStatuses: []api.ContainerStatus{ { Ready: false, RestartCount: 0, State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, }, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test7", "0/1", "Init:0/1", "3 (10s ago)", ""}}}, }, { // Test pod has 2 init containers, one restarting and the other not running, and 1 container not running api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test8"}, Spec: api.PodSpec{InitContainers: make([]api.Container, 2), Containers: make([]api.Container, 1)}, Status: api.PodStatus{ Phase: "podPhase", InitContainerStatuses: []api.ContainerStatus{ { Ready: false, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, }, { Ready: false, State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, }, }, ContainerStatuses: []api.ContainerStatus{ { Ready: false, State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, }, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test8", "0/1", "Init:0/2", "3 (10s ago)", ""}}}, }, { // Test pod has 2 init containers, one completed without restarts and the other restarting, and 1 container not running api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test9"}, Spec: api.PodSpec{InitContainers: make([]api.Container, 2), Containers: make([]api.Container, 1)}, Status: api.PodStatus{ Phase: "podPhase", InitContainerStatuses: []api.ContainerStatus{ { Ready: false, State: api.ContainerState{Terminated: &api.ContainerStateTerminated{}}, }, { Ready: false, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, }, }, ContainerStatuses: []api.ContainerStatus{ { Ready: false, State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, }, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test9", "0/1", "Init:1/2", "3 (10s ago)", ""}}}, }, { // Test pod has 2 init containers, one completed with restarts and the other restarting, and 1 container not running api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test10"}, Spec: api.PodSpec{InitContainers: make([]api.Container, 2), Containers: make([]api.Container, 1)}, Status: api.PodStatus{ Phase: "podPhase", InitContainerStatuses: []api.ContainerStatus{ { Ready: false, RestartCount: 2, State: api.ContainerState{Terminated: &api.ContainerStateTerminated{}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-2 * time.Minute))}}, }, { Ready: false, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, }, }, ContainerStatuses: []api.ContainerStatus{ { Ready: false, State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, }, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test10", "0/1", "Init:1/2", "5 (10s ago)", ""}}}, }, { // Test pod has 1 init container completed with restarts and one container restarting api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test11"}, Spec: api.PodSpec{InitContainers: make([]api.Container, 1), Containers: make([]api.Container, 1)}, Status: api.PodStatus{ Phase: "Running", InitContainerStatuses: []api.ContainerStatus{ { Ready: false, RestartCount: 2, State: api.ContainerState{Terminated: &api.ContainerStateTerminated{}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-2 * time.Minute))}}, }, }, ContainerStatuses: []api.ContainerStatus{ { Ready: false, RestartCount: 4, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * time.Second))}}, }, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test11", "0/1", "Running", "4 (20s ago)", ""}}}, }, { // Test pod has 1 container that restarted 5d ago api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test12"}, Spec: api.PodSpec{Containers: make([]api.Container, 1)}, Status: api.PodStatus{ Phase: "Running", ContainerStatuses: []api.ContainerStatus{ { Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-5 * 24 * time.Hour))}}, }, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test12", "1/1", "Running", "3 (5d ago)", ""}}}, }, { // Test pod has 2 containers, one has never restarted and the other has restarted 10d ago api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test13"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "Running", ContainerStatuses: []api.ContainerStatus{ { Ready: true, RestartCount: 0, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, }, { Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * 24 * time.Hour))}}, }, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test13", "2/2", "Running", "3 (10d ago)", ""}}}, }, { // Test pod has 2 containers, one restarted 5d ago and the other restarted 20d ago api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test14"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "Running", ContainerStatuses: []api.ContainerStatus{ { Ready: true, RestartCount: 6, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-5 * 24 * time.Hour))}}, }, { Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * 24 * time.Hour))}}, }, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test14", "2/2", "Running", "9 (5d ago)", ""}}}, }, { // Test PodScheduled condition with reason SchedulingGated api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test15"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "podPhase", Conditions: []api.PodCondition{ { Type: api.PodScheduled, Status: api.ConditionFalse, Reason: apiv1.PodReasonSchedulingGated, }, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test15", "0/2", apiv1.PodReasonSchedulingGated, "0", ""}}}, }, } for i, test := range tests { rows, err := printPod(&test.pod, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expect, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows)) } } } func TestPrintPodWithRestartableInitContainer(t *testing.T) { tests := []struct { pod api.Pod expect []metav1.TableRow }{ { // Test pod has 2 restartable init containers, the first one running but not started. api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test1"}, Spec: api.PodSpec{ InitContainers: []api.Container{ {Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways}, {Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways}, }, Containers: make([]api.Container, 1)}, Status: api.PodStatus{ Phase: "Pending", InitContainerStatuses: []api.ContainerStatus{ { Name: "restartable-init-1", Ready: false, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, Started: utilpointer.Bool(false), LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, }, { Name: "restartable-init-2", Ready: false, State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, Started: utilpointer.Bool(false), }, }, ContainerStatuses: []api.ContainerStatus{ { Ready: false, RestartCount: 0, State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, }, }, Conditions: []api.PodCondition{ {Type: api.PodInitialized, Status: api.ConditionFalse}, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test1", "0/3", "Init:0/2", "3 (10s ago)", ""}}}, }, { // Test pod has 2 restartable init containers, the first one started and the second one running but not started. api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test1"}, Spec: api.PodSpec{ InitContainers: []api.Container{ {Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways}, {Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways}, }, Containers: make([]api.Container, 1)}, Status: api.PodStatus{ Phase: "Pending", InitContainerStatuses: []api.ContainerStatus{ { Name: "restartable-init-1", Ready: false, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, Started: utilpointer.Bool(true), LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, }, { Name: "restartable-init-2", Ready: false, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, Started: utilpointer.Bool(false), }, }, ContainerStatuses: []api.ContainerStatus{ { Ready: false, RestartCount: 0, State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, }, }, Conditions: []api.PodCondition{ {Type: api.PodInitialized, Status: api.ConditionFalse}, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test1", "0/3", "Init:1/2", "3 (10s ago)", ""}}}, }, { // Test pod has 2 restartable init containers started and 1 container running api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test2"}, Spec: api.PodSpec{ InitContainers: []api.Container{ {Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways}, {Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways}, }, Containers: make([]api.Container, 1)}, Status: api.PodStatus{ Phase: "Running", InitContainerStatuses: []api.ContainerStatus{ { Name: "restartable-init-1", Ready: false, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, Started: utilpointer.Bool(true), LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, }, { Name: "restartable-init-2", Ready: false, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, Started: utilpointer.Bool(true), }, }, ContainerStatuses: []api.ContainerStatus{ { Ready: true, RestartCount: 4, State: api.ContainerState{Running: &api.ContainerStateRunning{}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * time.Second))}}, }, }, Conditions: []api.PodCondition{ {Type: api.PodInitialized, Status: api.ConditionTrue}, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test2", "1/3", "Running", "7 (10s ago)", ""}}}, }, { // Test pod has 2 restartable init containers completed with non-zero and 1 container completed api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test3"}, Spec: api.PodSpec{ InitContainers: []api.Container{ {Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways}, {Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways}, }, Containers: make([]api.Container, 1)}, Status: api.PodStatus{ Phase: "Succeeded", InitContainerStatuses: []api.ContainerStatus{ { Name: "restartable-init-1", Ready: false, RestartCount: 3, State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Error", ExitCode: 137}}, Started: utilpointer.Bool(false), LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}}, }, { Name: "restartable-init-2", Ready: false, State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Error", ExitCode: 137}}, Started: utilpointer.Bool(false), }, }, ContainerStatuses: []api.ContainerStatus{ { Ready: false, RestartCount: 4, State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Completed", ExitCode: 0}}, LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * time.Second))}}, }, }, Conditions: []api.PodCondition{ {Type: api.PodInitialized, Status: api.ConditionTrue}, }, }, }, []metav1.TableRow{ { Cells: []interface{}{"test3", "0/3", "Completed", "7 (10s ago)", ""}, Conditions: []metav1.TableRowCondition{ {Type: metav1.RowCompleted, Status: metav1.ConditionTrue, Reason: "Succeeded", Message: "The pod has completed successfully."}, }, }, }, }, } for i, test := range tests { rows, err := printPod(&test.pod, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expect, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows)) } } } func TestPrintPodwide(t *testing.T) { condition1 := "condition1" condition2 := "condition2" condition3 := "condition3" tests := []struct { pod api.Pod expect []metav1.TableRow }{ { // Test when the NodeName and PodIP are not none api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test1"}, Spec: api.PodSpec{ Containers: make([]api.Container, 2), NodeName: "test1", ReadinessGates: []api.PodReadinessGate{ { ConditionType: api.PodConditionType(condition1), }, { ConditionType: api.PodConditionType(condition2), }, { ConditionType: api.PodConditionType(condition3), }, }, }, Status: api.PodStatus{ Conditions: []api.PodCondition{ { Type: api.PodConditionType(condition1), Status: api.ConditionFalse, }, { Type: api.PodConditionType(condition2), Status: api.ConditionTrue, }, }, Phase: "podPhase", PodIPs: []api.PodIP{{IP: "1.1.1.1"}}, ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {RestartCount: 3}, }, NominatedNodeName: "node1", }, }, []metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", "6", "", "1.1.1.1", "test1", "node1", "1/3"}}}, }, { // Test when the NodeName and PodIP are not none api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test1"}, Spec: api.PodSpec{ Containers: make([]api.Container, 2), NodeName: "test1", ReadinessGates: []api.PodReadinessGate{ { ConditionType: api.PodConditionType(condition1), }, { ConditionType: api.PodConditionType(condition2), }, { ConditionType: api.PodConditionType(condition3), }, }, }, Status: api.PodStatus{ Conditions: []api.PodCondition{ { Type: api.PodConditionType(condition1), Status: api.ConditionFalse, }, { Type: api.PodConditionType(condition2), Status: api.ConditionTrue, }, }, Phase: "podPhase", PodIPs: []api.PodIP{{IP: "1.1.1.1"}, {IP: "2001:db8::"}}, ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {RestartCount: 3}, }, NominatedNodeName: "node1", }, }, []metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", "6", "", "1.1.1.1", "test1", "node1", "1/3"}}}, }, { // Test when the NodeName and PodIP are none api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test2"}, Spec: api.PodSpec{ Containers: make([]api.Container, 2), NodeName: "", }, Status: api.PodStatus{ Phase: "podPhase", ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {State: api.ContainerState{Waiting: &api.ContainerStateWaiting{Reason: "ContainerWaitingReason"}}, RestartCount: 3}, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test2", "1/2", "ContainerWaitingReason", "6", "", "", "", "", ""}}}, }, } for i, test := range tests { rows, err := printPod(&test.pod, printers.GenerateOptions{Wide: true}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expect, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows)) } } } func TestPrintPodConditions(t *testing.T) { runningPod := &api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test1", Labels: map[string]string{"a": "1", "b": "2"}}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "Running", ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {RestartCount: 3}, }, }, } succeededPod := &api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test1", Labels: map[string]string{"a": "1", "b": "2"}}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "Succeeded", ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {RestartCount: 3}, }, }, } failedPod := &api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test2", Labels: map[string]string{"b": "2"}}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "Failed", ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {RestartCount: 3}, }, }, } tests := []struct { pod *api.Pod expect []metav1.TableRow }{ // Should not have TableRowCondition { pod: runningPod, // Columns: Name, Ready, Reason, Restarts, Age expect: []metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "Running", "6", ""}}}, }, // Should have TableRowCondition: podSuccessConditions { pod: succeededPod, expect: []metav1.TableRow{ { // Columns: Name, Ready, Reason, Restarts, Age Cells: []interface{}{"test1", "1/2", "Succeeded", "6", ""}, Conditions: podSuccessConditions, }, }, }, // Should have TableRowCondition: podFailedCondition { pod: failedPod, expect: []metav1.TableRow{ { // Columns: Name, Ready, Reason, Restarts, Age Cells: []interface{}{"test2", "1/2", "Failed", "6", ""}, Conditions: podFailedConditions, }, }, }, } for i, test := range tests { rows, err := printPod(test.pod, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expect, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows)) } } } func TestPrintPodList(t *testing.T) { tests := []struct { pods api.PodList expect []metav1.TableRow }{ // Test podList's pod: name, num of containers, restarts, container ready status { api.PodList{ Items: []api.Pod{ { ObjectMeta: metav1.ObjectMeta{Name: "test1"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: "podPhase", ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, }, }, }, { ObjectMeta: metav1.ObjectMeta{Name: "test2"}, Spec: api.PodSpec{Containers: make([]api.Container, 1)}, Status: api.PodStatus{ Phase: "podPhase", ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 1, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, }, }, }, }, }, []metav1.TableRow{{Cells: []interface{}{"test1", "2/2", "podPhase", "6", ""}}, {Cells: []interface{}{"test2", "1/1", "podPhase", "1", ""}}}, }, } for _, test := range tests { rows, err := printPodList(&test.pods, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expect, rows) { t.Errorf("mismatch: %s", cmp.Diff(test.expect, rows)) } } } func TestPrintNonTerminatedPod(t *testing.T) { tests := []struct { pod api.Pod expect []metav1.TableRow }{ { // Test pod phase Running should be printed api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test1"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: api.PodRunning, ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {RestartCount: 3}, }, }, }, // Columns: Name, Ready, Reason, Restarts, Age []metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "Running", "6", ""}}}, }, { // Test pod phase Pending should be printed api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test2"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: api.PodPending, ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {RestartCount: 3}, }, }, }, // Columns: Name, Ready, Reason, Restarts, Age []metav1.TableRow{{Cells: []interface{}{"test2", "1/2", "Pending", "6", ""}}}, }, { // Test pod phase Unknown should be printed api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test3"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: api.PodUnknown, ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {RestartCount: 3}, }, }, }, // Columns: Name, Ready, Reason, Restarts, Age []metav1.TableRow{{Cells: []interface{}{"test3", "1/2", "Unknown", "6", ""}}}, }, { // Test pod phase Succeeded should be printed api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test4"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: api.PodSucceeded, ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {RestartCount: 3}, }, }, }, // Columns: Name, Ready, Reason, Restarts, Age []metav1.TableRow{ { Cells: []interface{}{"test4", "1/2", "Succeeded", "6", ""}, Conditions: podSuccessConditions, }, }, }, { // Test pod phase Failed shouldn't be printed api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test5"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, Status: api.PodStatus{ Phase: api.PodFailed, ContainerStatuses: []api.ContainerStatus{ {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, {Ready: true, RestartCount: 3}, }, }, }, // Columns: Name, Ready, Reason, Restarts, Age []metav1.TableRow{ { Cells: []interface{}{"test5", "1/2", "Failed", "6", ""}, Conditions: podFailedConditions, }, }, }, } for i, test := range tests { rows, err := printPod(&test.pod, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expect, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows)) } } } func TestPrintPodTemplate(t *testing.T) { tests := []struct { podTemplate api.PodTemplate options printers.GenerateOptions expected []metav1.TableRow }{ // Test basic pod template with no containers. { podTemplate: api.PodTemplate{ ObjectMeta: metav1.ObjectMeta{Name: "pod-template-1"}, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{Name: "pod-template-1"}, Spec: api.PodSpec{ Containers: []api.Container{}, }, }, }, options: printers.GenerateOptions{}, // Columns: Name, Containers, Images, Pod Labels expected: []metav1.TableRow{{Cells: []interface{}{"pod-template-1", "", "", ""}}}, }, // Test basic pod template with two containers. { podTemplate: api.PodTemplate{ ObjectMeta: metav1.ObjectMeta{Name: "pod-template-2"}, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{Name: "pod-template-2"}, Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-container1", Image: "fake-image1", }, { Name: "fake-container2", Image: "fake-image2", }, }, }, }, }, options: printers.GenerateOptions{}, // Columns: Name, Containers, Images, Pod Labels expected: []metav1.TableRow{{Cells: []interface{}{"pod-template-2", "fake-container1,fake-container2", "fake-image1,fake-image2", ""}}}, }, // Test basic pod template with pod labels { podTemplate: api.PodTemplate{ ObjectMeta: metav1.ObjectMeta{Name: "pod-template-3"}, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Name: "pod-template-3", Labels: map[string]string{"foo": "bar"}, }, Spec: api.PodSpec{ Containers: []api.Container{}, }, }, }, options: printers.GenerateOptions{}, // Columns: Name, Containers, Images, Pod Labels expected: []metav1.TableRow{{Cells: []interface{}{"pod-template-3", "", "", "foo=bar"}}}, }, } for i, test := range tests { rows, err := printPodTemplate(&test.podTemplate, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintPodTemplateList(t *testing.T) { templateList := api.PodTemplateList{ Items: []api.PodTemplate{ { ObjectMeta: metav1.ObjectMeta{Name: "pod-template-1"}, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Name: "pod-template-2", Labels: map[string]string{"foo": "bar"}, }, Spec: api.PodSpec{ Containers: []api.Container{}, }, }, }, { ObjectMeta: metav1.ObjectMeta{Name: "pod-template-2"}, Template: api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Name: "pod-template-2", Labels: map[string]string{"a": "b"}, }, Spec: api.PodSpec{ Containers: []api.Container{}, }, }, }, }, } // Columns: Name, Containers, Images, Pod Labels expectedRows := []metav1.TableRow{ {Cells: []interface{}{"pod-template-1", "", "", "foo=bar"}}, {Cells: []interface{}{"pod-template-2", "", "", "a=b"}}, } rows, err := printPodTemplateList(&templateList, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error printing pod template list: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(expectedRows, rows) { t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) } } type stringTestList []struct { name, got, exp string } func TestTranslateTimestampSince(t *testing.T) { tl := stringTestList{ {"a while from now", translateTimestampSince(metav1.Time{Time: time.Now().Add(2.1e9)}), ""}, {"almost now", translateTimestampSince(metav1.Time{Time: time.Now().Add(1.9e9)}), "0s"}, {"now", translateTimestampSince(metav1.Time{Time: time.Now()}), "0s"}, {"unknown", translateTimestampSince(metav1.Time{}), ""}, {"30 seconds ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-3e10)}), "30s"}, {"5 minutes ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-3e11)}), "5m"}, {"an hour ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-6e12)}), "100m"}, {"2 days ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}), "2d"}, {"months ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -90)}), "90d"}, {"10 years ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(-10, 0, 0)}), "10y"}, } for _, test := range tl { if test.got != test.exp { t.Errorf("On %v, expected '%v', but got '%v'", test.name, test.exp, test.got) } } } func TestTranslateTimestampUntil(t *testing.T) { // Since this method compares the time with time.Now() internally, // small buffers of 0.1 seconds are added on comparing times to consider method call overhead. // Otherwise, the output strings become shorter than expected. const buf = 1e8 tl := stringTestList{ {"a while ago", translateTimestampUntil(metav1.Time{Time: time.Now().Add(-2.1e9)}), ""}, {"almost now", translateTimestampUntil(metav1.Time{Time: time.Now().Add(-1.9e9)}), "0s"}, {"now", translateTimestampUntil(metav1.Time{Time: time.Now()}), "0s"}, {"unknown", translateTimestampUntil(metav1.Time{}), ""}, {"in 30 seconds", translateTimestampUntil(metav1.Time{Time: time.Now().Add(3e10 + buf)}), "30s"}, {"in 5 minutes", translateTimestampUntil(metav1.Time{Time: time.Now().Add(3e11 + buf)}), "5m"}, {"in an hour", translateTimestampUntil(metav1.Time{Time: time.Now().Add(6e12 + buf)}), "100m"}, {"in 2 days", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, 2).Add(buf)}), "2d"}, {"in months", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, 90).Add(buf)}), "90d"}, {"in 10 years", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(10, 0, 0).Add(buf)}), "10y"}, } for _, test := range tl { if test.got != test.exp { t.Errorf("On %v, expected '%v', but got '%v'", test.name, test.exp, test.got) } } } func TestPrintDeployment(t *testing.T) { testDeployment := apps.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: apps.DeploymentSpec{ Replicas: 5, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-container1", Image: "fake-image1", }, { Name: "fake-container2", Image: "fake-image2", }, }, }, }, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, }, Status: apps.DeploymentStatus{ Replicas: 10, UpdatedReplicas: 2, AvailableReplicas: 1, UnavailableReplicas: 4, }, } tests := []struct { deployment apps.Deployment options printers.GenerateOptions expected []metav1.TableRow }{ // Test Deployment with no generate options. { deployment: testDeployment, options: printers.GenerateOptions{}, // Columns: Name, ReadyReplicas, UpdatedReplicas, AvailableReplicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"test1", "0/5", int64(2), int64(1), "0s"}}}, }, // Test generate options: Wide. { deployment: testDeployment, options: printers.GenerateOptions{Wide: true}, // Columns: Name, ReadyReplicas, UpdatedReplicas, AvailableReplicas, Age, Containers, Images, Selectors expected: []metav1.TableRow{{Cells: []interface{}{"test1", "0/5", int64(2), int64(1), "0s", "fake-container1,fake-container2", "fake-image1,fake-image2", "foo=bar"}}}, }, } for i, test := range tests { rows, err := printDeployment(&test.deployment, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintDaemonSet(t *testing.T) { testDaemonSet := apps.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: apps.DaemonSetSpec{ Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-container1", Image: "fake-image1", }, { Name: "fake-container2", Image: "fake-image2", }, }, }, }, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, }, Status: apps.DaemonSetStatus{ CurrentNumberScheduled: 2, DesiredNumberScheduled: 3, NumberReady: 1, UpdatedNumberScheduled: 2, NumberAvailable: 0, }, } tests := []struct { daemonSet apps.DaemonSet options printers.GenerateOptions expected []metav1.TableRow }{ // Test generate daemon set with no generate options. { daemonSet: testDaemonSet, options: printers.GenerateOptions{}, // Columns: Name, Num Desired, Num Current, Num Ready, Num Updated, Num Available, Selectors, Age expected: []metav1.TableRow{{Cells: []interface{}{"test1", int64(3), int64(2), int64(1), int64(2), int64(0), "", "0s"}}}, }, // Test generate daemon set with "Wide" generate options. { daemonSet: testDaemonSet, options: printers.GenerateOptions{Wide: true}, // Columns: Name, Num Desired, Num Current, Num Ready, Num Updated, Num Available, Node Selectors, Age, Containers, Images, Labels expected: []metav1.TableRow{{Cells: []interface{}{"test1", int64(3), int64(2), int64(1), int64(2), int64(0), "", "0s", "fake-container1,fake-container2", "fake-image1,fake-image2", "foo=bar"}}}, }, } for i, test := range tests { rows, err := printDaemonSet(&test.daemonSet, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintDaemonSetList(t *testing.T) { daemonSetList := apps.DaemonSetList{ Items: []apps.DaemonSet{ { ObjectMeta: metav1.ObjectMeta{ Name: "daemonset1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: apps.DaemonSetSpec{ Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-container1", Image: "fake-image1", }, { Name: "fake-container2", Image: "fake-image2", }, }, }, }, }, Status: apps.DaemonSetStatus{ CurrentNumberScheduled: 2, DesiredNumberScheduled: 3, NumberReady: 1, UpdatedNumberScheduled: 2, NumberAvailable: 0, }, }, { ObjectMeta: metav1.ObjectMeta{ Name: "daemonset2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: apps.DaemonSetSpec{ Template: api.PodTemplateSpec{}, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, }, Status: apps.DaemonSetStatus{ CurrentNumberScheduled: 4, DesiredNumberScheduled: 2, NumberReady: 9, UpdatedNumberScheduled: 3, NumberAvailable: 3, }, }, }, } // Columns: Name, Num Desired, Num Current, Num Ready, Num Updated, Num Available, Selectors, Age expectedRows := []metav1.TableRow{ {Cells: []interface{}{"daemonset1", int64(3), int64(2), int64(1), int64(2), int64(0), "", "0s"}}, {Cells: []interface{}{"daemonset2", int64(2), int64(4), int64(9), int64(3), int64(3), "", "0s"}}, } rows, err := printDaemonSetList(&daemonSetList, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error printing daemon set list: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(expectedRows, rows) { t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) } } func TestPrintJob(t *testing.T) { now := time.Now() completions := int32(2) tests := []struct { job batch.Job options printers.GenerateOptions expected []metav1.TableRow }{ { // Generate table rows for Job with no generate options. job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.JobSpec{ Completions: &completions, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-job-container1", Image: "fake-job-image1", }, { Name: "fake-job-container2", Image: "fake-job-image2", }, }, }, }, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-lable-value"}}, }, Status: batch.JobStatus{ Succeeded: 1, }, }, options: printers.GenerateOptions{}, // Columns: Name, Status, Completions, Duration, Age expected: []metav1.TableRow{{Cells: []interface{}{"job1", "Running", "1/2", "", "0s"}}}, }, // Generate table rows for Job with generate options "Wide". { job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.JobSpec{ Completions: &completions, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-job-container1", Image: "fake-job-image1", }, { Name: "fake-job-container2", Image: "fake-job-image2", }, }, }, }, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-label-value"}}, }, Status: batch.JobStatus{ Succeeded: 1, }, }, options: printers.GenerateOptions{Wide: true}, // Columns: Name, Status, Completions, Duration, Age, Containers, Images, Selectors expected: []metav1.TableRow{ { Cells: []interface{}{"job1", "Running", "1/2", "", "0s", "fake-job-container1,fake-job-container2", "fake-job-image1,fake-job-image2", "job-label=job-label-value"}, }, }, }, // Job with ten-year age. { job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job2", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, Spec: batch.JobSpec{ Completions: nil, }, Status: batch.JobStatus{ Succeeded: 0, }, }, options: printers.GenerateOptions{}, // Columns: Name, Status, Completions, Duration, Age expected: []metav1.TableRow{{Cells: []interface{}{"job2", "Running", "0/1", "", "10y"}}}, }, // Job with duration. { job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job3", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, Spec: batch.JobSpec{ Completions: nil, }, Status: batch.JobStatus{ Succeeded: 0, StartTime: &metav1.Time{Time: now.Add(time.Minute)}, CompletionTime: &metav1.Time{Time: now.Add(31 * time.Minute)}, }, }, options: printers.GenerateOptions{}, // Columns: Name, Status, Completions, Duration, Age expected: []metav1.TableRow{{Cells: []interface{}{"job3", "Running", "0/1", "30m", "10y"}}}, }, { job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job4", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, Spec: batch.JobSpec{ Completions: nil, }, Status: batch.JobStatus{ Succeeded: 0, StartTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)}, }, }, options: printers.GenerateOptions{}, // Columns: Name, Status, Completions, Duration, Age expected: []metav1.TableRow{{Cells: []interface{}{"job4", "Running", "0/1", "20m", "10y"}}}, }, { job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job5", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.JobSpec{ Completions: nil, }, Status: batch.JobStatus{ Succeeded: 0, Conditions: []batch.JobCondition{ { Type: batch.JobComplete, Status: api.ConditionTrue, }, }, }, }, options: printers.GenerateOptions{}, // Columns: Name, Status, Completions, Duration, Age expected: []metav1.TableRow{{Cells: []interface{}{"job5", "Complete", "0/1", "", "0s"}}}, }, { job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job6", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.JobSpec{ Completions: nil, }, Status: batch.JobStatus{ Succeeded: 0, Conditions: []batch.JobCondition{ { Type: batch.JobFailed, Status: api.ConditionTrue, }, }, }, }, options: printers.GenerateOptions{}, // Columns: Name, Status, Completions, Duration, Age expected: []metav1.TableRow{{Cells: []interface{}{"job6", "Failed", "0/1", "", "0s"}}}, }, { job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job7", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.JobSpec{ Completions: nil, }, Status: batch.JobStatus{ Succeeded: 0, Conditions: []batch.JobCondition{ { Type: batch.JobSuspended, Status: api.ConditionTrue, }, }, }, }, options: printers.GenerateOptions{}, // Columns: Name, Status, Completions, Duration, Age expected: []metav1.TableRow{{Cells: []interface{}{"job7", "Suspended", "0/1", "", "0s"}}}, }, { job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job8", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.JobSpec{ Completions: nil, }, Status: batch.JobStatus{ Succeeded: 0, Conditions: []batch.JobCondition{ { Type: batch.JobFailureTarget, Status: api.ConditionTrue, }, }, }, }, options: printers.GenerateOptions{}, // Columns: Name, Status, Completions, Duration, Age expected: []metav1.TableRow{{Cells: []interface{}{"job8", "FailureTarget", "0/1", "", "0s"}}}, }, { job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job9", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, DeletionTimestamp: &metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.JobSpec{ Completions: nil, }, }, options: printers.GenerateOptions{}, // Columns: Name, Status, Completions, Duration, Age expected: []metav1.TableRow{{Cells: []interface{}{"job9", "Terminating", "0/1", "", "0s"}}}, }, } for i, test := range tests { rows, err := printJob(&test.job, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintJobList(t *testing.T) { completions := int32(2) jobList := batch.JobList{ Items: []batch.Job{ { ObjectMeta: metav1.ObjectMeta{ Name: "job1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.JobSpec{ Completions: &completions, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-job-container1", Image: "fake-job-image1", }, { Name: "fake-job-container2", Image: "fake-job-image2", }, }, }, }, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-lable-value"}}, }, Status: batch.JobStatus{ Succeeded: 1, }, }, { ObjectMeta: metav1.ObjectMeta{ Name: "job2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.JobSpec{ Completions: &completions, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-job-container1", Image: "fake-job-image1", }, { Name: "fake-job-container2", Image: "fake-job-image2", }, }, }, }, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-lable-value"}}, }, Status: batch.JobStatus{ Succeeded: 2, StartTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)}, }, }, }, } // Columns: Name, Status, Completions, Duration, Age expectedRows := []metav1.TableRow{ {Cells: []interface{}{"job1", "Running", "1/2", "", "0s"}}, {Cells: []interface{}{"job2", "Running", "2/2", "20m", "0s"}}, } rows, err := printJobList(&jobList, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error printing job list: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(expectedRows, rows) { t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) } } func TestPrintHPA(t *testing.T) { minReplicasVal := int32(2) targetUtilizationVal := int32(80) currentUtilizationVal := int32(50) metricLabelSelector, err := metav1.ParseToLabelSelector("label=value") if err != nil { t.Errorf("unable to parse label selector: %v", err) } tests := []struct { hpa autoscaling.HorizontalPodAutoscaler expected []metav1.TableRow }{ // minReplicas unset { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MaxReplicas: 10, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "", "", int64(10), int64(4), ""}}}, }, // external source type, target average value (no current) { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ExternalMetricSourceType, External: &autoscaling.ExternalMetricSource{ Metric: autoscaling.MetricIdentifier{ Name: "some-external-metric", Selector: metricLabelSelector, }, Target: autoscaling.MetricTarget{ Type: autoscaling.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "/100m (avg)", "2", int64(10), int64(4), ""}}}, }, // external source type, target average value { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ExternalMetricSourceType, External: &autoscaling.ExternalMetricSource{ Metric: autoscaling.MetricIdentifier{ Name: "some-external-metric", Selector: metricLabelSelector, }, Target: autoscaling.MetricTarget{ Type: autoscaling.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, CurrentMetrics: []autoscaling.MetricStatus{ { Type: autoscaling.ExternalMetricSourceType, External: &autoscaling.ExternalMetricStatus{ Metric: autoscaling.MetricIdentifier{ Name: "some-external-metric", Selector: metricLabelSelector, }, Current: autoscaling.MetricValueStatus{ AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, }, }, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m (avg)", "2", int64(10), int64(4), ""}}}, }, // external source type, target value (no current) { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ExternalMetricSourceType, External: &autoscaling.ExternalMetricSource{ Metric: autoscaling.MetricIdentifier{ Name: "some-service-metric", Selector: metricLabelSelector, }, Target: autoscaling.MetricTarget{ Type: autoscaling.ValueMetricType, Value: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "/100m", "2", int64(10), int64(4), ""}}}, }, // external source type, target value { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ExternalMetricSourceType, External: &autoscaling.ExternalMetricSource{ Metric: autoscaling.MetricIdentifier{ Name: "some-external-metric", Selector: metricLabelSelector, }, Target: autoscaling.MetricTarget{ Type: autoscaling.ValueMetricType, Value: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, CurrentMetrics: []autoscaling.MetricStatus{ { Type: autoscaling.ExternalMetricSourceType, External: &autoscaling.ExternalMetricStatus{ Metric: autoscaling.MetricIdentifier{ Name: "some-external-metric", }, Current: autoscaling.MetricValueStatus{ Value: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, }, }, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), ""}}}, }, // pods source type (no current) { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.PodsMetricSourceType, Pods: &autoscaling.PodsMetricSource{ Metric: autoscaling.MetricIdentifier{ Name: "some-pods-metric", }, Target: autoscaling.MetricTarget{ Type: autoscaling.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "/100m", "2", int64(10), int64(4), ""}}}, }, // pods source type { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.PodsMetricSourceType, Pods: &autoscaling.PodsMetricSource{ Metric: autoscaling.MetricIdentifier{ Name: "some-pods-metric", }, Target: autoscaling.MetricTarget{ Type: autoscaling.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, CurrentMetrics: []autoscaling.MetricStatus{ { Type: autoscaling.PodsMetricSourceType, Pods: &autoscaling.PodsMetricStatus{ Metric: autoscaling.MetricIdentifier{ Name: "some-pods-metric", }, Current: autoscaling.MetricValueStatus{ AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, }, }, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), ""}}}, }, // object source type (no current) { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ObjectMetricSourceType, Object: &autoscaling.ObjectMetricSource{ DescribedObject: autoscaling.CrossVersionObjectReference{ Name: "some-service", Kind: "Service", }, Metric: autoscaling.MetricIdentifier{ Name: "some-service-metric", }, Target: autoscaling.MetricTarget{ Type: autoscaling.ValueMetricType, Value: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "/100m", "2", int64(10), int64(4), ""}}}, }, // object source type { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ObjectMetricSourceType, Object: &autoscaling.ObjectMetricSource{ DescribedObject: autoscaling.CrossVersionObjectReference{ Name: "some-service", Kind: "Service", }, Metric: autoscaling.MetricIdentifier{ Name: "some-service-metric", }, Target: autoscaling.MetricTarget{ Type: autoscaling.ValueMetricType, Value: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, CurrentMetrics: []autoscaling.MetricStatus{ { Type: autoscaling.ObjectMetricSourceType, Object: &autoscaling.ObjectMetricStatus{ DescribedObject: autoscaling.CrossVersionObjectReference{ Name: "some-service", Kind: "Service", }, Metric: autoscaling.MetricIdentifier{ Name: "some-service-metric", }, Current: autoscaling.MetricValueStatus{ Value: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, }, }, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), ""}}}, }, // resource source type, targetVal (no current) { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ResourceMetricSourceType, Resource: &autoscaling.ResourceMetricSource{ Name: api.ResourceCPU, Target: autoscaling.MetricTarget{ Type: autoscaling.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "cpu: /100m", "2", int64(10), int64(4), ""}}}, }, // resource source type, targetVal { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ResourceMetricSourceType, Resource: &autoscaling.ResourceMetricSource{ Name: api.ResourceCPU, Target: autoscaling.MetricTarget{ Type: autoscaling.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, CurrentMetrics: []autoscaling.MetricStatus{ { Type: autoscaling.ResourceMetricSourceType, Resource: &autoscaling.ResourceMetricStatus{ Name: api.ResourceCPU, Current: autoscaling.MetricValueStatus{ AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, }, }, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "cpu: 50m/100m", "2", int64(10), int64(4), ""}}}, }, // resource source type, targetUtil (no current) { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ResourceMetricSourceType, Resource: &autoscaling.ResourceMetricSource{ Name: api.ResourceCPU, Target: autoscaling.MetricTarget{ Type: autoscaling.UtilizationMetricType, AverageUtilization: &targetUtilizationVal, }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "cpu: /80%", "2", int64(10), int64(4), ""}}}, }, // resource source type, targetUtil { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ResourceMetricSourceType, Resource: &autoscaling.ResourceMetricSource{ Name: api.ResourceCPU, Target: autoscaling.MetricTarget{ Type: autoscaling.UtilizationMetricType, AverageUtilization: &targetUtilizationVal, }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, CurrentMetrics: []autoscaling.MetricStatus{ { Type: autoscaling.ResourceMetricSourceType, Resource: &autoscaling.ResourceMetricStatus{ Name: api.ResourceCPU, Current: autoscaling.MetricValueStatus{ AverageUtilization: ¤tUtilizationVal, AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI), }, }, }, }, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "cpu: 50%/80%", "2", int64(10), int64(4), ""}}}, }, // container resource source type, targetVal (no current) { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ContainerResourceMetricSourceType, ContainerResource: &autoscaling.ContainerResourceMetricSource{ Name: api.ResourceCPU, Container: "application", Target: autoscaling.MetricTarget{ Type: autoscaling.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "cpu: /100m", "2", int64(10), int64(4), ""}}}, }, // container resource source type, targetVal { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ContainerResourceMetricSourceType, ContainerResource: &autoscaling.ContainerResourceMetricSource{ Name: api.ResourceCPU, Target: autoscaling.MetricTarget{ Type: autoscaling.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, CurrentMetrics: []autoscaling.MetricStatus{ { Type: autoscaling.ContainerResourceMetricSourceType, ContainerResource: &autoscaling.ContainerResourceMetricStatus{ Name: api.ResourceCPU, Container: "application", Current: autoscaling.MetricValueStatus{ AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, }, }, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "cpu: 50m/100m", "2", int64(10), int64(4), ""}}}, }, // container resource source type, targetUtil (no current) { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ContainerResourceMetricSourceType, ContainerResource: &autoscaling.ContainerResourceMetricSource{ Name: api.ResourceCPU, Container: "application", Target: autoscaling.MetricTarget{ Type: autoscaling.UtilizationMetricType, AverageUtilization: &targetUtilizationVal, }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "cpu: /80%", "2", int64(10), int64(4), ""}}}, }, // container resource source type, targetUtil { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.ContainerResourceMetricSourceType, ContainerResource: &autoscaling.ContainerResourceMetricSource{ Name: api.ResourceCPU, Target: autoscaling.MetricTarget{ Type: autoscaling.UtilizationMetricType, AverageUtilization: &targetUtilizationVal, }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, CurrentMetrics: []autoscaling.MetricStatus{ { Type: autoscaling.ContainerResourceMetricSourceType, ContainerResource: &autoscaling.ContainerResourceMetricStatus{ Name: api.ResourceCPU, Container: "application", Current: autoscaling.MetricValueStatus{ AverageUtilization: ¤tUtilizationVal, AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI), }, }, }, }, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "cpu: 50%/80%", "2", int64(10), int64(4), ""}}}, }, // multiple specs { hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ Name: "some-rc", Kind: "ReplicationController", }, MinReplicas: &minReplicasVal, MaxReplicas: 10, Metrics: []autoscaling.MetricSpec{ { Type: autoscaling.PodsMetricSourceType, Pods: &autoscaling.PodsMetricSource{ Metric: autoscaling.MetricIdentifier{ Name: "some-pods-metric", }, Target: autoscaling.MetricTarget{ Type: autoscaling.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI), }, }, }, { Type: autoscaling.ResourceMetricSourceType, Resource: &autoscaling.ResourceMetricSource{ Name: api.ResourceCPU, Target: autoscaling.MetricTarget{ Type: autoscaling.UtilizationMetricType, AverageUtilization: &targetUtilizationVal, }, }, }, { Type: autoscaling.PodsMetricSourceType, Pods: &autoscaling.PodsMetricSource{ Metric: autoscaling.MetricIdentifier{ Name: "other-pods-metric", }, Target: autoscaling.MetricTarget{ Type: autoscaling.AverageValueMetricType, AverageValue: resource.NewMilliQuantity(400, resource.DecimalSI), }, }, }, }, }, Status: autoscaling.HorizontalPodAutoscalerStatus{ CurrentReplicas: 4, DesiredReplicas: 5, CurrentMetrics: []autoscaling.MetricStatus{ { Type: autoscaling.PodsMetricSourceType, Pods: &autoscaling.PodsMetricStatus{ Metric: autoscaling.MetricIdentifier{ Name: "some-pods-metric", }, Current: autoscaling.MetricValueStatus{ AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI), }, }, }, { Type: autoscaling.ResourceMetricSourceType, Resource: &autoscaling.ResourceMetricStatus{ Name: api.ResourceCPU, Current: autoscaling.MetricValueStatus{ AverageUtilization: ¤tUtilizationVal, AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI), }, }, }, }, }, }, // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m, cpu: 50%/80% + 1 more...", "2", int64(10), int64(4), ""}}}, }, } for i, test := range tests { rows, err := printHorizontalPodAutoscaler(&test.hpa, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintService(t *testing.T) { singleExternalIP := []string{"80.11.12.10"} mulExternalIP := []string{"80.11.12.10", "80.11.12.11"} tests := []struct { service api.Service options printers.GenerateOptions expected []metav1.TableRow }{ { // Test name, cluster ip, port with protocol service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test1"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeClusterIP, Ports: []api.ServicePort{ { Protocol: "tcp", Port: 2233, }, }, ClusterIPs: []string{"10.9.8.7"}, Selector: map[string]string{"foo": "bar"}, // Does NOT get printed. }, }, options: printers.GenerateOptions{}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age expected: []metav1.TableRow{{Cells: []interface{}{"test1", "ClusterIP", "10.9.8.7", "", "2233/tcp", ""}}}, }, { // Test generate options: Wide includes selectors. service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test1"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeClusterIP, Ports: []api.ServicePort{ { Protocol: "tcp", Port: 2233, }, }, ClusterIPs: []string{"10.9.8.7"}, Selector: map[string]string{"foo": "bar"}, }, }, options: printers.GenerateOptions{Wide: true}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age, Selector expected: []metav1.TableRow{{Cells: []interface{}{"test1", "ClusterIP", "10.9.8.7", "", "2233/tcp", "", "foo=bar"}}}, }, { // Test NodePort service service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test2"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeNodePort, Ports: []api.ServicePort{ { Protocol: "tcp", Port: 8888, NodePort: 9999, }, }, ClusterIPs: []string{"10.9.8.7"}, }, }, options: printers.GenerateOptions{}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age expected: []metav1.TableRow{{Cells: []interface{}{"test2", "NodePort", "10.9.8.7", "", "8888:9999/tcp", ""}}}, }, { // Test LoadBalancer service service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test3"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeLoadBalancer, Ports: []api.ServicePort{ { Protocol: "tcp", Port: 8888, }, }, ClusterIPs: []string{"10.9.8.7"}, }, }, options: printers.GenerateOptions{}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age expected: []metav1.TableRow{{Cells: []interface{}{"test3", "LoadBalancer", "10.9.8.7", "", "8888/tcp", ""}}}, }, { // Test LoadBalancer service with single ExternalIP and no LoadBalancerStatus service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test4"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeLoadBalancer, Ports: []api.ServicePort{ { Protocol: "tcp", Port: 8888, }, }, ClusterIPs: []string{"10.9.8.7"}, ExternalIPs: singleExternalIP, }, }, options: printers.GenerateOptions{}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age expected: []metav1.TableRow{{Cells: []interface{}{"test4", "LoadBalancer", "10.9.8.7", "80.11.12.10", "8888/tcp", ""}}}, }, { // Test LoadBalancer service with single ExternalIP service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test5"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeLoadBalancer, Ports: []api.ServicePort{ { Protocol: "tcp", Port: 8888, }, }, ClusterIPs: []string{"10.9.8.7"}, ExternalIPs: singleExternalIP, }, Status: api.ServiceStatus{ LoadBalancer: api.LoadBalancerStatus{ Ingress: []api.LoadBalancerIngress{ { IP: "3.4.5.6", Hostname: "test.cluster.com", }, }, }, }, }, options: printers.GenerateOptions{}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age expected: []metav1.TableRow{{Cells: []interface{}{"test5", "LoadBalancer", "10.9.8.7", "3.4.5.6,80.11.12.10", "8888/tcp", ""}}}, }, { // Test LoadBalancer service with mul ExternalIPs service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test6"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeLoadBalancer, Ports: []api.ServicePort{ { Protocol: "tcp", Port: 8888, }, }, ClusterIPs: []string{"10.9.8.7"}, ExternalIPs: mulExternalIP, }, Status: api.ServiceStatus{ LoadBalancer: api.LoadBalancerStatus{ Ingress: []api.LoadBalancerIngress{ { IP: "2.3.4.5", Hostname: "test.cluster.local", }, { IP: "3.4.5.6", Hostname: "test.cluster.com", }, }, }, }, }, options: printers.GenerateOptions{}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age 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", ""}}}, }, { // Test ExternalName service service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test7"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeExternalName, ExternalName: "my.database.example.com", }, }, options: printers.GenerateOptions{}, // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age expected: []metav1.TableRow{{Cells: []interface{}{"test7", "ExternalName", "", "my.database.example.com", "", ""}}}, }, } for i, test := range tests { rows, err := printService(&test.service, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintServiceList(t *testing.T) { serviceList := api.ServiceList{ Items: []api.Service{ { ObjectMeta: metav1.ObjectMeta{Name: "service1"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeClusterIP, Ports: []api.ServicePort{ { Protocol: "tcp", Port: 2233, }, }, ClusterIPs: []string{"10.9.8.7"}, }, }, { ObjectMeta: metav1.ObjectMeta{Name: "service2"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeNodePort, Ports: []api.ServicePort{ { Protocol: "udp", Port: 5566, }, }, ClusterIPs: []string{"1.2.3.4"}, }, }, }, } // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age expectedRows := []metav1.TableRow{ {Cells: []interface{}{"service1", "ClusterIP", "10.9.8.7", "", "2233/tcp", ""}}, {Cells: []interface{}{"service2", "NodePort", "1.2.3.4", "", "5566/udp", ""}}, } rows, err := printServiceList(&serviceList, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error printing service list: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(expectedRows, rows) { t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) } } func TestPrintPodDisruptionBudget(t *testing.T) { minAvailable := intstr.FromInt32(22) maxUnavailable := intstr.FromInt32(11) tests := []struct { pdb policy.PodDisruptionBudget expected []metav1.TableRow }{ // Min Available set, no Max Available. { pdb: policy.PodDisruptionBudget{ ObjectMeta: metav1.ObjectMeta{ Namespace: "ns1", Name: "pdb1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: policy.PodDisruptionBudgetSpec{ MinAvailable: &minAvailable, }, Status: policy.PodDisruptionBudgetStatus{ DisruptionsAllowed: 5, }, }, // Columns: Name, Min Available, Max Available, Allowed Disruptions, Age expected: []metav1.TableRow{{Cells: []interface{}{"pdb1", "22", "N/A", int64(5), "0s"}}}, }, // Max Available set, no Min Available. { pdb: policy.PodDisruptionBudget{ ObjectMeta: metav1.ObjectMeta{ Namespace: "ns2", Name: "pdb2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: policy.PodDisruptionBudgetSpec{ MaxUnavailable: &maxUnavailable, }, Status: policy.PodDisruptionBudgetStatus{ DisruptionsAllowed: 5, }, }, // Columns: Name, Min Available, Max Available, Allowed Disruptions, Age expected: []metav1.TableRow{{Cells: []interface{}{"pdb2", "N/A", "11", int64(5), "0s"}}}, }} for i, test := range tests { rows, err := printPodDisruptionBudget(&test.pdb, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintPodDisruptionBudgetList(t *testing.T) { minAvailable := intstr.FromInt32(22) maxUnavailable := intstr.FromInt32(11) pdbList := policy.PodDisruptionBudgetList{ Items: []policy.PodDisruptionBudget{ { ObjectMeta: metav1.ObjectMeta{ Namespace: "ns1", Name: "pdb1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: policy.PodDisruptionBudgetSpec{ MaxUnavailable: &maxUnavailable, }, Status: policy.PodDisruptionBudgetStatus{ DisruptionsAllowed: 5, }, }, { ObjectMeta: metav1.ObjectMeta{ Namespace: "ns2", Name: "pdb2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: policy.PodDisruptionBudgetSpec{ MinAvailable: &minAvailable, }, Status: policy.PodDisruptionBudgetStatus{ DisruptionsAllowed: 3, }, }, }, } // Columns: Name, Min Available, Max Available, Allowed Disruptions, Age expectedRows := []metav1.TableRow{ {Cells: []interface{}{"pdb1", "N/A", "11", int64(5), "0s"}}, {Cells: []interface{}{"pdb2", "22", "N/A", int64(3), "0s"}}, } rows, err := printPodDisruptionBudgetList(&pdbList, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error printing pod template list: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(expectedRows, rows) { t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) } } func TestPrintControllerRevision(t *testing.T) { tests := []struct { history apps.ControllerRevision expected []metav1.TableRow }{ { history: apps.ControllerRevision{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, OwnerReferences: []metav1.OwnerReference{ { Controller: boolP(true), APIVersion: "apps/v1", Kind: "DaemonSet", Name: "foo", }, }, }, Revision: 1, }, expected: []metav1.TableRow{{Cells: []interface{}{"test1", "daemonset.apps/foo", int64(1), "0s"}}}, }, { history: apps.ControllerRevision{ ObjectMeta: metav1.ObjectMeta{ Name: "test2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, OwnerReferences: []metav1.OwnerReference{ { Controller: boolP(false), Kind: "ABC", Name: "foo", }, }, }, Revision: 2, }, expected: []metav1.TableRow{{Cells: []interface{}{"test2", "", int64(2), "0s"}}}, }, { history: apps.ControllerRevision{ ObjectMeta: metav1.ObjectMeta{ Name: "test3", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, OwnerReferences: []metav1.OwnerReference{}, }, Revision: 3, }, expected: []metav1.TableRow{{Cells: []interface{}{"test3", "", int64(3), "0s"}}}, }, { history: apps.ControllerRevision{ ObjectMeta: metav1.ObjectMeta{ Name: "test4", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, OwnerReferences: nil, }, Revision: 4, }, expected: []metav1.TableRow{{Cells: []interface{}{"test4", "", int64(4), "0s"}}}, }, } for i, test := range tests { rows, err := printControllerRevision(&test.history, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func boolP(b bool) *bool { return &b } func TestPrintConfigMap(t *testing.T) { tests := []struct { configMap api.ConfigMap expected []metav1.TableRow }{ // Basic config map with no data. { configMap: api.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "configmap1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, }, // Columns: Name, Data, Age expected: []metav1.TableRow{{Cells: []interface{}{"configmap1", int64(0), "0s"}}}, }, // Basic config map with one data entry { configMap: api.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "configmap2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Data: map[string]string{ "foo": "bar", }, }, // Columns: Name, (Num) Data, Age expected: []metav1.TableRow{{Cells: []interface{}{"configmap2", int64(1), "0s"}}}, }, // Basic config map with one data and one binary data entry. { configMap: api.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "configmap3", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Data: map[string]string{ "foo": "bar", }, BinaryData: map[string][]byte{ "bin": []byte("binary data"), }, }, // Columns: Name, (Num) Data, Age expected: []metav1.TableRow{{Cells: []interface{}{"configmap3", int64(2), "0s"}}}, }, } for i, test := range tests { rows, err := printConfigMap(&test.configMap, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintNetworkPolicy(t *testing.T) { tests := []struct { policy networking.NetworkPolicy expected []metav1.TableRow }{ // Basic network policy with empty spec. { policy: networking.NetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "policy1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: networking.NetworkPolicySpec{}, }, // Columns: Name, Pod-Selector, Age expected: []metav1.TableRow{{Cells: []interface{}{"policy1", "", "0s"}}}, }, // Basic network policy with pod selector. { policy: networking.NetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "policy2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: networking.NetworkPolicySpec{ PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, }, }, // Columns: Name, Pod-Selector, Age expected: []metav1.TableRow{{Cells: []interface{}{"policy2", "foo=bar", "0s"}}}, }, } for i, test := range tests { rows, err := printNetworkPolicy(&test.policy, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintRoleBinding(t *testing.T) { tests := []struct { binding rbac.RoleBinding options printers.GenerateOptions expected []metav1.TableRow }{ // Basic role binding { binding: rbac.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Subjects: []rbac.Subject{ { Kind: "User", Name: "system:kube-controller-manager", }, }, RoleRef: rbac.RoleRef{ Kind: "Role", Name: "extension-apiserver-authentication-reader", }, }, options: printers.GenerateOptions{}, // Columns: Name, Age expected: []metav1.TableRow{{Cells: []interface{}{"binding1", "Role/extension-apiserver-authentication-reader", "0s"}}}, }, // Generate options=Wide; print subject and roles. { binding: rbac.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Subjects: []rbac.Subject{ { Kind: "User", Name: "user-name", }, { Kind: "Group", Name: "group-name", }, { Kind: "ServiceAccount", Name: "service-account-name", Namespace: "service-account-namespace", }, }, RoleRef: rbac.RoleRef{ Kind: "Role", Name: "role-name", }, }, options: printers.GenerateOptions{Wide: true}, // Columns: Name, Age, Role, Users, Groups, ServiceAccounts expected: []metav1.TableRow{{Cells: []interface{}{"binding2", "Role/role-name", "0s", "user-name", "group-name", "service-account-namespace/service-account-name"}}}, }, } for i, test := range tests { rows, err := printRoleBinding(&test.binding, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintClusterRoleBinding(t *testing.T) { tests := []struct { binding rbac.ClusterRoleBinding options printers.GenerateOptions expected []metav1.TableRow }{ // Basic cluster role binding { binding: rbac.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Subjects: []rbac.Subject{ { Kind: "User", Name: "system:kube-controller-manager", }, }, RoleRef: rbac.RoleRef{ Kind: "Role", Name: "extension-apiserver-authentication-reader", }, }, options: printers.GenerateOptions{}, // Columns: Name, Age expected: []metav1.TableRow{{Cells: []interface{}{"binding1", "Role/extension-apiserver-authentication-reader", "0s"}}}, }, // Generate options=Wide; print subject and roles. { binding: rbac.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "binding2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Subjects: []rbac.Subject{ { Kind: "User", Name: "user-name", }, { Kind: "Group", Name: "group-name", }, { Kind: "ServiceAccount", Name: "service-account-name", Namespace: "service-account-namespace", }, }, RoleRef: rbac.RoleRef{ Kind: "Role", Name: "role-name", }, }, options: printers.GenerateOptions{Wide: true}, // Columns: Name, Age, Role, Users, Groups, ServiceAccounts expected: []metav1.TableRow{{Cells: []interface{}{"binding2", "Role/role-name", "0s", "user-name", "group-name", "service-account-namespace/service-account-name"}}}, }, } for i, test := range tests { rows, err := printClusterRoleBinding(&test.binding, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintCertificateSigningRequest(t *testing.T) { tests := []struct { csr certificates.CertificateSigningRequest expected []metav1.TableRow }{ // Basic CSR with no spec or status; defaults to status: Pending. { csr: certificates.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ Name: "csr1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: certificates.CertificateSigningRequestSpec{}, Status: certificates.CertificateSigningRequestStatus{}, }, // Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition expected: []metav1.TableRow{{Cells: []interface{}{"csr1", "0s", "", "", "", "Pending"}}}, }, // Basic CSR with Spec and Status=Approved. { csr: certificates.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ Name: "csr2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: certificates.CertificateSigningRequestSpec{ Username: "CSR Requestor", }, Status: certificates.CertificateSigningRequestStatus{ Conditions: []certificates.CertificateSigningRequestCondition{ { Type: certificates.CertificateApproved, }, }, }, }, // Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition expected: []metav1.TableRow{{Cells: []interface{}{"csr2", "0s", "", "CSR Requestor", "", "Approved"}}}, }, // Basic CSR with Spec and SignerName set { csr: certificates.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ Name: "csr2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: certificates.CertificateSigningRequestSpec{ Username: "CSR Requestor", SignerName: "example.com/test-signer", }, Status: certificates.CertificateSigningRequestStatus{ Conditions: []certificates.CertificateSigningRequestCondition{ { Type: certificates.CertificateApproved, }, }, }, }, // Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition expected: []metav1.TableRow{{Cells: []interface{}{"csr2", "0s", "example.com/test-signer", "CSR Requestor", "", "Approved"}}}, }, // Basic CSR with Spec, SignerName and ExpirationSeconds set { csr: certificates.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ Name: "csr2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: certificates.CertificateSigningRequestSpec{ Username: "CSR Requestor", SignerName: "example.com/test-signer", ExpirationSeconds: csr.DurationToExpirationSeconds(7*24*time.Hour + time.Hour), // a little bit more than a week }, Status: certificates.CertificateSigningRequestStatus{ Conditions: []certificates.CertificateSigningRequestCondition{ { Type: certificates.CertificateApproved, }, }, }, }, // Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition expected: []metav1.TableRow{{Cells: []interface{}{"csr2", "0s", "example.com/test-signer", "CSR Requestor", "7d1h", "Approved"}}}, }, // Basic CSR with Spec and Status=Approved; certificate issued. { csr: certificates.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ Name: "csr2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: certificates.CertificateSigningRequestSpec{ Username: "CSR Requestor", }, Status: certificates.CertificateSigningRequestStatus{ Conditions: []certificates.CertificateSigningRequestCondition{ { Type: certificates.CertificateApproved, }, }, Certificate: []byte("cert data"), }, }, // Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition expected: []metav1.TableRow{{Cells: []interface{}{"csr2", "0s", "", "CSR Requestor", "", "Approved,Issued"}}}, }, // Basic CSR with Spec and Status=Denied. { csr: certificates.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ Name: "csr3", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: certificates.CertificateSigningRequestSpec{ Username: "CSR Requestor", }, Status: certificates.CertificateSigningRequestStatus{ Conditions: []certificates.CertificateSigningRequestCondition{ { Type: certificates.CertificateDenied, }, }, }, }, // Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition expected: []metav1.TableRow{{Cells: []interface{}{"csr3", "0s", "", "CSR Requestor", "", "Denied"}}}, }, } for i, test := range tests { rows, err := printCertificateSigningRequest(&test.csr, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintReplicationController(t *testing.T) { tests := []struct { rc api.ReplicationController options printers.GenerateOptions expected []metav1.TableRow }{ // Basic print replication controller without replicas or status. { rc: api.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "rc1", Namespace: "test-namespace", }, Spec: api.ReplicationControllerSpec{ Selector: map[string]string{"a": "b"}, Template: &api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b"}, }, Spec: api.PodSpec{ Containers: []api.Container{ { Name: "test", Image: "test_image", ImagePullPolicy: api.PullIfNotPresent, TerminationMessagePolicy: api.TerminationMessageReadFile, }, }, RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, }, }, }, }, options: printers.GenerateOptions{}, // Columns: Name, Desired, Current, Ready, Age expected: []metav1.TableRow{{Cells: []interface{}{"rc1", int64(0), int64(0), int64(0), ""}}}, }, // Basic print replication controller with replicas; does not print containers or labels { rc: api.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "rc1", Namespace: "test-namespace", }, Spec: api.ReplicationControllerSpec{ Replicas: 5, Selector: map[string]string{"a": "b"}, Template: &api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b"}, }, Spec: api.PodSpec{ Containers: []api.Container{ { Name: "test", Image: "test_image", ImagePullPolicy: api.PullIfNotPresent, TerminationMessagePolicy: api.TerminationMessageReadFile, }, }, RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, }, }, }, Status: api.ReplicationControllerStatus{ Replicas: 3, ReadyReplicas: 1, }, }, options: printers.GenerateOptions{}, // Columns: Name, Desired, Current, Ready, Age expected: []metav1.TableRow{{Cells: []interface{}{"rc1", int64(5), int64(3), int64(1), ""}}}, }, // Generate options: Wide; print containers and labels. { rc: api.ReplicationController{ ObjectMeta: metav1.ObjectMeta{ Name: "rc1", }, Spec: api.ReplicationControllerSpec{ Replicas: 5, Selector: map[string]string{"a": "b"}, Template: &api.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"a": "b"}, }, Spec: api.PodSpec{ Containers: []api.Container{ { Name: "test", Image: "test_image", ImagePullPolicy: api.PullIfNotPresent, TerminationMessagePolicy: api.TerminationMessageReadFile, }, }, RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, }, }, }, Status: api.ReplicationControllerStatus{ Replicas: 3, ReadyReplicas: 1, }, }, options: printers.GenerateOptions{Wide: true}, // Columns: Name, Desired, Current, Ready, Age, Containers, Images, Selector expected: []metav1.TableRow{{Cells: []interface{}{"rc1", int64(5), int64(3), int64(1), "", "test", "test_image", "a=b"}}}, }, } for i, test := range tests { rows, err := printReplicationController(&test.rc, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintReplicaSet(t *testing.T) { tests := []struct { replicaSet apps.ReplicaSet options printers.GenerateOptions expected []metav1.TableRow }{ // Generate options empty { replicaSet: apps.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: apps.ReplicaSetSpec{ Replicas: 5, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-container1", Image: "fake-image1", }, { Name: "fake-container2", Image: "fake-image2", }, }, }, }, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, }, Status: apps.ReplicaSetStatus{ Replicas: 5, ReadyReplicas: 2, }, }, options: printers.GenerateOptions{}, // Columns: Name, Desired, Current, Ready, Age expected: []metav1.TableRow{{Cells: []interface{}{"test1", int64(5), int64(5), int64(2), "0s"}}}, }, // Generate options "Wide" { replicaSet: apps.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: apps.ReplicaSetSpec{ Replicas: 5, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-container1", Image: "fake-image1", }, { Name: "fake-container2", Image: "fake-image2", }, }, }, }, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, }, Status: apps.ReplicaSetStatus{ Replicas: 5, ReadyReplicas: 2, }, }, options: printers.GenerateOptions{Wide: true}, // Columns: Name, Desired, Current, Ready, Age, Containers, Images, Selector expected: []metav1.TableRow{{Cells: []interface{}{"test1", int64(5), int64(5), int64(2), "0s", "fake-container1,fake-container2", "fake-image1,fake-image2", "foo=bar"}}}, }, } for i, test := range tests { rows, err := printReplicaSet(&test.replicaSet, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintReplicaSetList(t *testing.T) { replicaSetList := apps.ReplicaSetList{ Items: []apps.ReplicaSet{ { ObjectMeta: metav1.ObjectMeta{ Name: "replicaset1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: apps.ReplicaSetSpec{ Replicas: 5, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-container1", Image: "fake-image1", }, { Name: "fake-container2", Image: "fake-image2", }, }, }, }, }, Status: apps.ReplicaSetStatus{ Replicas: 5, ReadyReplicas: 2, }, }, { ObjectMeta: metav1.ObjectMeta{ Name: "replicaset2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: apps.ReplicaSetSpec{ Replicas: 4, Template: api.PodTemplateSpec{}, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, }, Status: apps.ReplicaSetStatus{ Replicas: 3, ReadyReplicas: 1, }, }, }, } // Columns: Name, Desired, Current, Ready, Age expectedRows := []metav1.TableRow{ {Cells: []interface{}{"replicaset1", int64(5), int64(5), int64(2), "0s"}}, {Cells: []interface{}{"replicaset2", int64(4), int64(3), int64(1), "0s"}}, } rows, err := printReplicaSetList(&replicaSetList, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error printing replica set list: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(expectedRows, rows) { t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) } } func TestPrintStatefulSet(t *testing.T) { tests := []struct { statefulSet apps.StatefulSet options printers.GenerateOptions expected []metav1.TableRow }{ // Basic stateful set; no generate options. { statefulSet: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: apps.StatefulSetSpec{ Replicas: 5, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-container1", Image: "fake-image1", }, { Name: "fake-container2", Image: "fake-image2", }, }, }, }, }, Status: apps.StatefulSetStatus{ Replicas: 5, ReadyReplicas: 2, }, }, options: printers.GenerateOptions{}, // Columns: Name, Ready, Age expected: []metav1.TableRow{{Cells: []interface{}{"test1", "2/5", "0s"}}}, }, // Generate options "Wide"; includes containers and images. { statefulSet: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: apps.StatefulSetSpec{ Replicas: 5, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-container1", Image: "fake-image1", }, { Name: "fake-container2", Image: "fake-image2", }, }, }, }, }, Status: apps.StatefulSetStatus{ Replicas: 5, ReadyReplicas: 2, }, }, options: printers.GenerateOptions{Wide: true}, // Columns: Name, Ready, Age, Containers, Images expected: []metav1.TableRow{{Cells: []interface{}{"test1", "2/5", "0s", "fake-container1,fake-container2", "fake-image1,fake-image2"}}}, }, } for i, test := range tests { rows, err := printStatefulSet(&test.statefulSet, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintPersistentVolume(t *testing.T) { myScn := "my-scn" myVacn := "my-vacn" claimRef := api.ObjectReference{ Name: "test", Namespace: "default", } tests := []struct { pv api.PersistentVolume expected []metav1.TableRow }{ { // Test bound pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", }, Spec: api.PersistentVolumeSpec{ ClaimRef: &claimRef, AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("4Gi"), }, }, Status: api.PersistentVolumeStatus{ Phase: api.VolumeBound, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test1", "4Gi", "ROX", "", "Bound", "default/test", "", "", "", "", ""}}}, }, { // Test failed pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test2", }, Spec: api.PersistentVolumeSpec{ ClaimRef: &claimRef, AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("4Gi"), }, }, Status: api.PersistentVolumeStatus{ Phase: api.VolumeFailed, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test2", "4Gi", "ROX", "", "Failed", "default/test", "", "", "", "", ""}}}, }, { // Test pending pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test3", }, Spec: api.PersistentVolumeSpec{ ClaimRef: &claimRef, AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteMany}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("10Gi"), }, }, Status: api.PersistentVolumeStatus{ Phase: api.VolumePending, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test3", "10Gi", "RWX", "", "Pending", "default/test", "", "", "", "", ""}}}, }, { // Test pending, storageClass pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test4", }, Spec: api.PersistentVolumeSpec{ ClaimRef: &claimRef, StorageClassName: myScn, AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("10Gi"), }, }, Status: api.PersistentVolumeStatus{ Phase: api.VolumePending, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test4", "10Gi", "RWO", "", "Pending", "default/test", "my-scn", "", "", "", ""}}}, }, { // Test pending, storageClass, volumeAttributesClass pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test4", }, Spec: api.PersistentVolumeSpec{ ClaimRef: &claimRef, StorageClassName: myScn, VolumeAttributesClassName: &myVacn, AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("10Gi"), }, }, Status: api.PersistentVolumeStatus{ Phase: api.VolumePending, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test4", "10Gi", "RWO", "", "Pending", "default/test", "my-scn", "my-vacn", "", "", ""}}}, }, { // Test available pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test5", }, Spec: api.PersistentVolumeSpec{ ClaimRef: &claimRef, StorageClassName: myScn, AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("10Gi"), }, }, Status: api.PersistentVolumeStatus{ Phase: api.VolumeAvailable, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test5", "10Gi", "RWO", "", "Available", "default/test", "my-scn", "", "", "", ""}}}, }, { // Test released pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test6", }, Spec: api.PersistentVolumeSpec{ ClaimRef: &claimRef, StorageClassName: myScn, AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("10Gi"), }, }, Status: api.PersistentVolumeStatus{ Phase: api.VolumeReleased, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test6", "10Gi", "RWO", "", "Released", "default/test", "my-scn", "", "", "", ""}}}, }, } for i, test := range tests { rows, err := printPersistentVolume(&test.pv, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintPersistentVolumeClaim(t *testing.T) { volumeMode := api.PersistentVolumeFilesystem myVacn := "my-vacn" myScn := "my-scn" tests := []struct { pvc api.PersistentVolumeClaim expected []metav1.TableRow }{ { // Test name, num of containers, restarts, container ready status pvc: api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "my-volume", VolumeMode: &volumeMode, }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimBound, AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("4Gi"), }, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test1", "Bound", "my-volume", "4Gi", "ROX", "", "", "", "Filesystem"}}}, }, { // Test name, num of containers, restarts, container ready status pvc: api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test2", }, Spec: api.PersistentVolumeClaimSpec{ VolumeMode: &volumeMode, }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimLost, AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("4Gi"), }, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test2", "Lost", "", "", "", "", "", "", "Filesystem"}}}, }, { // Test name, num of containers, restarts, container ready status pvc: api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test3", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "my-volume", VolumeMode: &volumeMode, }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimPending, AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteMany}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("10Gi"), }, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test3", "Pending", "my-volume", "10Gi", "RWX", "", "", "", "Filesystem"}}}, }, { // Test name, num of containers, restarts, container ready status pvc: api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test4", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "my-volume", StorageClassName: &myScn, VolumeMode: &volumeMode, }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimPending, AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("10Gi"), }, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test4", "Pending", "my-volume", "10Gi", "RWO", "my-scn", "", "", "Filesystem"}}}, }, { // Test name, num of containers, restarts, container ready status pvc: api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test5", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "my-volume", StorageClassName: &myScn, }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimPending, AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("10Gi"), }, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test5", "Pending", "my-volume", "10Gi", "RWO", "my-scn", "", "", ""}}}, }, { // Test name, num of containers, restarts, container ready status pvc: api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test5", }, Spec: api.PersistentVolumeClaimSpec{ VolumeName: "my-volume", StorageClassName: &myScn, VolumeAttributesClassName: &myVacn, }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimPending, AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Capacity: map[api.ResourceName]resource.Quantity{ api.ResourceStorage: resource.MustParse("10Gi"), }, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"test5", "Pending", "my-volume", "10Gi", "RWO", "my-scn", "my-vacn", "", ""}}}, }, } for i, test := range tests { rows, err := printPersistentVolumeClaim(&test.pvc, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintComponentStatus(t *testing.T) { tests := []struct { componentStatus api.ComponentStatus expected []metav1.TableRow }{ // Basic component status without conditions { componentStatus: api.ComponentStatus{ ObjectMeta: metav1.ObjectMeta{ Name: "cs1", }, Conditions: []api.ComponentCondition{}, }, // Columns: Name, Status, Message, Error expected: []metav1.TableRow{{Cells: []interface{}{"cs1", "Unknown", "", ""}}}, }, // Basic component status with healthy condition. { componentStatus: api.ComponentStatus{ ObjectMeta: metav1.ObjectMeta{ Name: "cs2", }, Conditions: []api.ComponentCondition{ { Type: "Healthy", Status: api.ConditionTrue, Message: "test message", Error: "test error", }, }, }, // Columns: Name, Status, Message, Error expected: []metav1.TableRow{{Cells: []interface{}{"cs2", "Healthy", "test message", "test error"}}}, }, // Basic component status with healthy condition. { componentStatus: api.ComponentStatus{ ObjectMeta: metav1.ObjectMeta{ Name: "cs3", }, Conditions: []api.ComponentCondition{ { Type: "Healthy", Status: api.ConditionFalse, Message: "test message", Error: "test error", }, }, }, // Columns: Name, Status, Message, Error expected: []metav1.TableRow{{Cells: []interface{}{"cs3", "Unhealthy", "test message", "test error"}}}, }, } for i, test := range tests { rows, err := printComponentStatus(&test.componentStatus, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintCronJob(t *testing.T) { timeZone := "LOCAL" completions := int32(2) suspend := false tests := []struct { cronjob batch.CronJob options printers.GenerateOptions expected []metav1.TableRow }{ // Basic cron job; does not print containers, images, or labels. { cronjob: batch.CronJob{ ObjectMeta: metav1.ObjectMeta{ Name: "cronjob1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.CronJobSpec{ Schedule: "0/5 * * * ?", Suspend: &suspend, JobTemplate: batch.JobTemplateSpec{ Spec: batch.JobSpec{ Completions: &completions, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-job-container1", Image: "fake-job-image1", }, { Name: "fake-job-container2", Image: "fake-job-image2", }, }, }, }, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, }, }, }, Status: batch.CronJobStatus{ LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)}, }, }, options: printers.GenerateOptions{}, // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age expected: []metav1.TableRow{{Cells: []interface{}{"cronjob1", "0/5 * * * ?", "", "False", int64(0), "0s", "0s"}}}, }, // Basic cron job; does not print containers, images, or labels. { cronjob: batch.CronJob{ ObjectMeta: metav1.ObjectMeta{ Name: "cronjob1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.CronJobSpec{ Schedule: "0/5 * * * ?", TimeZone: &timeZone, Suspend: &suspend, JobTemplate: batch.JobTemplateSpec{ Spec: batch.JobSpec{ Completions: &completions, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-job-container1", Image: "fake-job-image1", }, { Name: "fake-job-container2", Image: "fake-job-image2", }, }, }, }, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, }, }, }, Status: batch.CronJobStatus{ LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)}, }, }, options: printers.GenerateOptions{}, // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age expected: []metav1.TableRow{{Cells: []interface{}{"cronjob1", "0/5 * * * ?", "LOCAL", "False", int64(0), "0s", "0s"}}}, }, // Generate options: Wide; prints containers, images, and labels. { cronjob: batch.CronJob{ ObjectMeta: metav1.ObjectMeta{ Name: "cronjob1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.CronJobSpec{ Schedule: "0/5 * * * ?", Suspend: &suspend, JobTemplate: batch.JobTemplateSpec{ Spec: batch.JobSpec{ Completions: &completions, Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ { Name: "fake-job-container1", Image: "fake-job-image1", }, { Name: "fake-job-container2", Image: "fake-job-image2", }, }, }, }, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, }, }, }, Status: batch.CronJobStatus{ LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)}, }, }, options: printers.GenerateOptions{Wide: true}, // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age expected: []metav1.TableRow{{Cells: []interface{}{"cronjob1", "0/5 * * * ?", "", "False", int64(0), "0s", "0s", "fake-job-container1,fake-job-container2", "fake-job-image1,fake-job-image2", "a=b"}}}, }, // CronJob with Last Schedule and Age { cronjob: batch.CronJob{ ObjectMeta: metav1.ObjectMeta{ Name: "cronjob2", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Spec: batch.CronJobSpec{ Schedule: "0/5 * * * ?", Suspend: &suspend, }, Status: batch.CronJobStatus{ LastScheduleTime: &metav1.Time{Time: time.Now().Add(-3e10)}, }, }, options: printers.GenerateOptions{}, // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age expected: []metav1.TableRow{{Cells: []interface{}{"cronjob2", "0/5 * * * ?", "", "False", int64(0), "30s", "5m"}}}, }, // CronJob without Last Schedule { cronjob: batch.CronJob{ ObjectMeta: metav1.ObjectMeta{ Name: "cronjob3", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Spec: batch.CronJobSpec{ Schedule: "0/5 * * * ?", Suspend: &suspend, }, Status: batch.CronJobStatus{}, }, options: printers.GenerateOptions{}, // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age expected: []metav1.TableRow{{Cells: []interface{}{"cronjob3", "0/5 * * * ?", "", "False", int64(0), "", "5m"}}}, }, } for i, test := range tests { rows, err := printCronJob(&test.cronjob, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintCronJobList(t *testing.T) { timeZone := "LOCAL" completions := int32(2) suspend := false cronJobList := batch.CronJobList{ Items: []batch.CronJob{ { ObjectMeta: metav1.ObjectMeta{ Name: "cronjob1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.CronJobSpec{ Schedule: "0/5 * * * ?", TimeZone: &timeZone, Suspend: &suspend, JobTemplate: batch.JobTemplateSpec{ Spec: batch.JobSpec{ Completions: &completions, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, }, }, }, Status: batch.CronJobStatus{ LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)}, }, }, { ObjectMeta: metav1.ObjectMeta{ Name: "cronjob2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: batch.CronJobSpec{ Schedule: "4/5 1 1 1 ?", Suspend: &suspend, JobTemplate: batch.JobTemplateSpec{ Spec: batch.JobSpec{ Completions: &completions, Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, }, }, }, Status: batch.CronJobStatus{ LastScheduleTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)}, }, }, }, } // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age expectedRows := []metav1.TableRow{ {Cells: []interface{}{"cronjob1", "0/5 * * * ?", "LOCAL", "False", int64(0), "0s", "0s"}}, {Cells: []interface{}{"cronjob2", "4/5 1 1 1 ?", "", "False", int64(0), "20m", "0s"}}, } rows, err := printCronJobList(&cronJobList, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error printing job list: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(expectedRows, rows) { t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows)) } } func TestPrintStorageClass(t *testing.T) { policyDelte := api.PersistentVolumeReclaimDelete policyRetain := api.PersistentVolumeReclaimRetain bindModeImmediate := storage.VolumeBindingImmediate bindModeWait := storage.VolumeBindingWaitForFirstConsumer tests := []struct { sc storage.StorageClass expected []metav1.TableRow }{ { sc: storage.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "sc1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Provisioner: "kubernetes.io/glusterfs", }, expected: []metav1.TableRow{{Cells: []interface{}{"sc1", "kubernetes.io/glusterfs", "Delete", "Immediate", false, "0s"}}}, }, { sc: storage.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "sc2", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Provisioner: "kubernetes.io/nfs", }, expected: []metav1.TableRow{{Cells: []interface{}{"sc2", "kubernetes.io/nfs", "Delete", "Immediate", false, "5m"}}}, }, { sc: storage.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "sc3", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Provisioner: "kubernetes.io/nfs", ReclaimPolicy: &policyDelte, }, expected: []metav1.TableRow{{Cells: []interface{}{"sc3", "kubernetes.io/nfs", "Delete", "Immediate", false, "5m"}}}, }, { sc: storage.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "sc4", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Provisioner: "kubernetes.io/nfs", ReclaimPolicy: &policyRetain, VolumeBindingMode: &bindModeImmediate, }, expected: []metav1.TableRow{{Cells: []interface{}{"sc4", "kubernetes.io/nfs", "Retain", "Immediate", false, "5m"}}}, }, { sc: storage.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "sc5", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Provisioner: "kubernetes.io/nfs", ReclaimPolicy: &policyRetain, VolumeBindingMode: &bindModeWait, }, expected: []metav1.TableRow{{Cells: []interface{}{"sc5", "kubernetes.io/nfs", "Retain", "WaitForFirstConsumer", false, "5m"}}}, }, { sc: storage.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "sc6", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Provisioner: "kubernetes.io/nfs", ReclaimPolicy: &policyRetain, AllowVolumeExpansion: boolP(true), VolumeBindingMode: &bindModeWait, }, expected: []metav1.TableRow{{Cells: []interface{}{"sc6", "kubernetes.io/nfs", "Retain", "WaitForFirstConsumer", true, "5m"}}}, }, } for i, test := range tests { rows, err := printStorageClass(&test.sc, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintVolumeAttributesClass(t *testing.T) { tests := []struct { vac storage.VolumeAttributesClass expected []metav1.TableRow }{ { vac: storage.VolumeAttributesClass{ ObjectMeta: metav1.ObjectMeta{ Name: "vac1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, DriverName: "fake", }, expected: []metav1.TableRow{{Cells: []interface{}{"vac1", "fake", "0s"}}}, }, { vac: storage.VolumeAttributesClass{ ObjectMeta: metav1.ObjectMeta{ Name: "vac2", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, DriverName: "fake", Parameters: map[string]string{ "iops": "500", "throughput": "50MiB/s", }, }, expected: []metav1.TableRow{{Cells: []interface{}{"vac2", "fake", "5m"}}}, }, } for i, test := range tests { rows, err := printVolumeAttributesClass(&test.vac, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintLease(t *testing.T) { holder1 := "holder1" holder2 := "holder2" tests := []struct { lease coordination.Lease expected []metav1.TableRow }{ { lease: coordination.Lease{ ObjectMeta: metav1.ObjectMeta{ Name: "lease1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: coordination.LeaseSpec{ HolderIdentity: &holder1, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"lease1", "holder1", "0s"}}}, }, { lease: coordination.Lease{ ObjectMeta: metav1.ObjectMeta{ Name: "lease2", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Spec: coordination.LeaseSpec{ HolderIdentity: &holder2, }, }, expected: []metav1.TableRow{{Cells: []interface{}{"lease2", "holder2", "5m"}}}, }, } for i, test := range tests { rows, err := printLease(&test.lease, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintPriorityClass(t *testing.T) { tests := []struct { pc scheduling.PriorityClass expected []metav1.TableRow }{ { pc: scheduling.PriorityClass{ ObjectMeta: metav1.ObjectMeta{ Name: "pc1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Value: 1, }, expected: []metav1.TableRow{{Cells: []interface{}{"pc1", int64(1), bool(false), "0s"}}}, }, { pc: scheduling.PriorityClass{ ObjectMeta: metav1.ObjectMeta{ Name: "pc2", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Value: 1000000000, GlobalDefault: true, }, expected: []metav1.TableRow{{Cells: []interface{}{"pc2", int64(1000000000), bool(true), "5m"}}}, }, } for i, test := range tests { rows, err := printPriorityClass(&test.pc, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintRuntimeClass(t *testing.T) { tests := []struct { rc nodeapi.RuntimeClass expected []metav1.TableRow }{ { rc: nodeapi.RuntimeClass{ ObjectMeta: metav1.ObjectMeta{ Name: "rc1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Handler: "h1", }, expected: []metav1.TableRow{{Cells: []interface{}{"rc1", "h1", "0s"}}}, }, { rc: nodeapi.RuntimeClass{ ObjectMeta: metav1.ObjectMeta{ Name: "rc2", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Handler: "h2", }, expected: []metav1.TableRow{{Cells: []interface{}{"rc2", "h2", "5m"}}}, }, } for i, test := range tests { rows, err := printRuntimeClass(&test.rc, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintEndpoint(t *testing.T) { tests := []struct { endpoint api.Endpoints expected []metav1.TableRow }{ // Basic endpoint with no IP's { endpoint: api.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "endpoint1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, }, // Columns: Name, Endpoints, Age expected: []metav1.TableRow{{Cells: []interface{}{"endpoint1", "", "0s"}}}, }, // Endpoint with no ports { endpoint: api.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "endpoint3", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Subsets: []api.EndpointSubset{ { Addresses: []api.EndpointAddress{ { IP: "1.2.3.4", }, { IP: "5.6.7.8", }, }, }, }, }, // Columns: Name, Endpoints, Age expected: []metav1.TableRow{{Cells: []interface{}{"endpoint3", "1.2.3.4,5.6.7.8", "5m"}}}, }, // Basic endpoint with two IP's and one port { endpoint: api.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "endpoint2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Subsets: []api.EndpointSubset{ { Addresses: []api.EndpointAddress{ { IP: "1.2.3.4", }, { IP: "5.6.7.8", }, }, Ports: []api.EndpointPort{ { Port: 8001, Protocol: "tcp", }, }, }, }, }, // Columns: Name, Endpoints, Age expected: []metav1.TableRow{{Cells: []interface{}{"endpoint2", "1.2.3.4:8001,5.6.7.8:8001", "0s"}}}, }, // Basic endpoint with greater than three IP's triggering "more" string { endpoint: api.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "endpoint2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Subsets: []api.EndpointSubset{ { Addresses: []api.EndpointAddress{ { IP: "1.2.3.4", }, { IP: "5.6.7.8", }, { IP: "9.8.7.6", }, { IP: "6.6.6.6", }, }, Ports: []api.EndpointPort{ { Port: 8001, Protocol: "tcp", }, }, }, }, }, // Columns: Name, Endpoints, Age expected: []metav1.TableRow{{Cells: []interface{}{"endpoint2", "1.2.3.4:8001,5.6.7.8:8001,9.8.7.6:8001 + 1 more...", "0s"}}}, }, } for i, test := range tests { rows, err := printEndpoints(&test.endpoint, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintEndpointSlice(t *testing.T) { tcpProtocol := api.ProtocolTCP tests := []struct { endpointSlice discovery.EndpointSlice expected []metav1.TableRow }{ { endpointSlice: discovery.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ Name: "abcslice.123", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Port: utilpointer.Int32Ptr(80), Protocol: &tcpProtocol, }}, Endpoints: []discovery.Endpoint{{ Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"}, }}, }, // Columns: Name, AddressType, Ports, Endpoints, Age expected: []metav1.TableRow{{Cells: []interface{}{"abcslice.123", "IPv4", "80", "10.1.2.3,2001:db8::1234:5678", "0s"}}}, }, { endpointSlice: discovery.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ Name: "longerslicename.123", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, AddressType: discovery.AddressTypeIPv6, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Port: utilpointer.Int32Ptr(80), Protocol: &tcpProtocol, }, { Name: utilpointer.StringPtr("https"), Port: utilpointer.Int32Ptr(443), Protocol: &tcpProtocol, }}, Endpoints: []discovery.Endpoint{{ Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"}, }, { Addresses: []string{"10.2.3.4", "2001:db8::2345:6789"}, }}, }, // Columns: Name, AddressType, Ports, Endpoints, Age 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"}}}, }, { endpointSlice: discovery.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ Name: "multiportslice.123", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, AddressType: discovery.AddressTypeIPv4, Ports: []discovery.EndpointPort{{ Name: utilpointer.StringPtr("http"), Port: utilpointer.Int32Ptr(80), Protocol: &tcpProtocol, }, { Name: utilpointer.StringPtr("https"), Port: utilpointer.Int32Ptr(443), Protocol: &tcpProtocol, }, { Name: utilpointer.StringPtr("extra1"), Port: utilpointer.Int32Ptr(3000), Protocol: &tcpProtocol, }, { Name: utilpointer.StringPtr("extra2"), Port: utilpointer.Int32Ptr(3001), Protocol: &tcpProtocol, }}, Endpoints: []discovery.Endpoint{{ Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"}, }, { Addresses: []string{"10.2.3.4", "2001:db8::2345:6789"}, }}, }, // Columns: Name, AddressType, Ports, Endpoints, Age 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"}}}, }, } for i, test := range tests { rows, err := printEndpointSlice(&test.endpointSlice, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintFlowSchema(t *testing.T) { all := []string{"*"} tests := []struct { fs flowcontrol.FlowSchema expected []metav1.TableRow }{ { fs: flowcontrol.FlowSchema{ ObjectMeta: metav1.ObjectMeta{ Name: "all-matcher", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: flowcontrol.FlowSchemaSpec{ PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{Name: "allee"}, MatchingPrecedence: math.MaxInt32, DistinguisherMethod: &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByUserType}, Rules: []flowcontrol.PolicyRulesWithSubjects{{ Subjects: []flowcontrol.Subject{{ Kind: flowcontrol.SubjectKindGroup, Group: &flowcontrol.GroupSubject{Name: "system:authenticated"}, }}, ResourceRules: []flowcontrol.ResourcePolicyRule{{ Verbs: all, APIGroups: all, Resources: all, ClusterScope: true, Namespaces: all, }}, }, { Subjects: []flowcontrol.Subject{{ Kind: flowcontrol.SubjectKindGroup, Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"}, }}, ResourceRules: []flowcontrol.ResourcePolicyRule{{ Verbs: all, APIGroups: all, Resources: all, ClusterScope: true, Namespaces: all, }}, }, { Subjects: []flowcontrol.Subject{{ Kind: flowcontrol.SubjectKindGroup, Group: &flowcontrol.GroupSubject{Name: "system:authenticated"}, }, { Kind: flowcontrol.SubjectKindGroup, Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"}, }}, NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ Verbs: all, NonResourceURLs: all, }}, }}, }, }, // Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, Age, MissingPL expected: []metav1.TableRow{{Cells: []interface{}{"all-matcher", "allee", int64(math.MaxInt32), "ByUser", "0s", "?"}}}, }, { fs: flowcontrol.FlowSchema{ ObjectMeta: metav1.ObjectMeta{ Name: "some-matcher", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Spec: flowcontrol.FlowSchemaSpec{ PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{Name: "allee"}, MatchingPrecedence: 0, DistinguisherMethod: &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByNamespaceType}, Rules: []flowcontrol.PolicyRulesWithSubjects{{ Subjects: []flowcontrol.Subject{{ Kind: flowcontrol.SubjectKindGroup, Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"}, }}, ResourceRules: []flowcontrol.ResourcePolicyRule{{ Verbs: all, APIGroups: all, Resources: all, ClusterScope: true, Namespaces: all, }}, }, { Subjects: []flowcontrol.Subject{{ Kind: flowcontrol.SubjectKindGroup, Group: &flowcontrol.GroupSubject{Name: "system:authenticated"}, }, { Kind: flowcontrol.SubjectKindGroup, Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"}, }}, NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ Verbs: all, NonResourceURLs: all, }}, }}, }, Status: flowcontrol.FlowSchemaStatus{ Conditions: []flowcontrol.FlowSchemaCondition{{ Type: flowcontrol.FlowSchemaConditionDangling, Status: "True", LastTransitionTime: metav1.Time{Time: time.Now().Add(-time.Hour)}, }}, }, }, // Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, Age, MissingPL expected: []metav1.TableRow{{Cells: []interface{}{"some-matcher", "allee", int64(0), "ByNamespace", "5m", "True"}}}, }, { fs: flowcontrol.FlowSchema{ ObjectMeta: metav1.ObjectMeta{ Name: "exempt", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Spec: flowcontrol.FlowSchemaSpec{ PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{Name: "allee"}, MatchingPrecedence: 0, DistinguisherMethod: nil, Rules: []flowcontrol.PolicyRulesWithSubjects{{ Subjects: []flowcontrol.Subject{{ Kind: flowcontrol.SubjectKindGroup, Group: &flowcontrol.GroupSubject{Name: "system:masters"}, }}, ResourceRules: []flowcontrol.ResourcePolicyRule{{ Verbs: all, APIGroups: all, Resources: all, ClusterScope: true, Namespaces: all, }}, }}, }, }, // Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, Age, MissingPL expected: []metav1.TableRow{{Cells: []interface{}{"exempt", "allee", int64(0), "", "5m", "?"}}}, }, } for i, test := range tests { rows, err := printFlowSchema(&test.fs, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintPriorityLevelConfiguration(t *testing.T) { tests := []struct { pl flowcontrol.PriorityLevelConfiguration expected []metav1.TableRow }{ { pl: flowcontrol.PriorityLevelConfiguration{ ObjectMeta: metav1.ObjectMeta{ Name: "unlimited", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: flowcontrol.PriorityLevelConfigurationSpec{ Type: flowcontrol.PriorityLevelEnablementExempt, }, }, // Columns: Name, Type, NominalConcurrencyShares, Queues, HandSize, QueueLengthLimit, Age expected: []metav1.TableRow{{Cells: []interface{}{"unlimited", "Exempt", "", "", "", "", "0s"}}}, }, { pl: flowcontrol.PriorityLevelConfiguration{ ObjectMeta: metav1.ObjectMeta{ Name: "unqueued", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: flowcontrol.PriorityLevelConfigurationSpec{ Type: flowcontrol.PriorityLevelEnablementLimited, Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ NominalConcurrencyShares: 47, LimitResponse: flowcontrol.LimitResponse{ Type: flowcontrol.LimitResponseTypeReject, }, }, }, }, // Columns: Name, Type, NominalConcurrencyShares, Queues, HandSize, QueueLengthLimit, Age expected: []metav1.TableRow{{Cells: []interface{}{"unqueued", "Limited", int32(47), "", "", "", "0s"}}}, }, { pl: flowcontrol.PriorityLevelConfiguration{ ObjectMeta: metav1.ObjectMeta{ Name: "queued", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: flowcontrol.PriorityLevelConfigurationSpec{ Type: flowcontrol.PriorityLevelEnablementLimited, Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ NominalConcurrencyShares: 42, LimitResponse: flowcontrol.LimitResponse{ Type: flowcontrol.LimitResponseTypeQueue, Queuing: &flowcontrol.QueuingConfiguration{ Queues: 8, HandSize: 3, QueueLengthLimit: 4, }, }, }, }, }, // Columns: Name, Type, NominalConcurrencyShares, Queues, HandSize, QueueLengthLimit, Age expected: []metav1.TableRow{{Cells: []interface{}{"queued", "Limited", int32(42), int32(8), int32(3), int32(4), "0s"}}}, }, } for i, test := range tests { rows, err := printPriorityLevelConfiguration(&test.pl, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintStorageVersion(t *testing.T) { commonEncodingVersion := "v1" tests := []struct { sv apiserverinternal.StorageVersion expected []metav1.TableRow }{ { sv: apiserverinternal.StorageVersion{ ObjectMeta: metav1.ObjectMeta{ Name: "empty", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Status: apiserverinternal.StorageVersionStatus{}, }, // Columns: Name, CommonEncodingVersion, StorageVersions, Age expected: []metav1.TableRow{{Cells: []interface{}{"empty", "", "", "0s"}}}, }, { sv: apiserverinternal.StorageVersion{ ObjectMeta: metav1.ObjectMeta{ Name: "valid", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Status: apiserverinternal.StorageVersionStatus{ StorageVersions: []apiserverinternal.ServerStorageVersion{ { APIServerID: "1", EncodingVersion: "v1", DecodableVersions: []string{"v1"}, }, { APIServerID: "2", EncodingVersion: "v1", DecodableVersions: []string{"v1", "v2"}, }, }, CommonEncodingVersion: &commonEncodingVersion, }, }, // Columns: Name, CommonEncodingVersion, StorageVersions, Age expected: []metav1.TableRow{{Cells: []interface{}{"valid", "v1", "1=v1,2=v1", "0s"}}}, }, { sv: apiserverinternal.StorageVersion{ ObjectMeta: metav1.ObjectMeta{ Name: "disagree", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Status: apiserverinternal.StorageVersionStatus{ StorageVersions: []apiserverinternal.ServerStorageVersion{ { APIServerID: "1", EncodingVersion: "v1", DecodableVersions: []string{"v1"}, }, { APIServerID: "2", EncodingVersion: "v1", DecodableVersions: []string{"v1", "v2"}, }, { APIServerID: "3", EncodingVersion: "v2", DecodableVersions: []string{"v2"}, }, }, }, }, // Columns: Name, CommonEncodingVersion, StorageVersions, Age expected: []metav1.TableRow{{Cells: []interface{}{"disagree", "", "1=v1,2=v1,3=v2", "0s"}}}, }, { sv: apiserverinternal.StorageVersion{ ObjectMeta: metav1.ObjectMeta{ Name: "agreeWithMore", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Status: apiserverinternal.StorageVersionStatus{ StorageVersions: []apiserverinternal.ServerStorageVersion{ { APIServerID: "1", EncodingVersion: "v1", DecodableVersions: []string{"v1"}, }, { APIServerID: "2", EncodingVersion: "v1", DecodableVersions: []string{"v1", "v2"}, }, { APIServerID: "3", EncodingVersion: "v1", DecodableVersions: []string{"v1", "v2"}, }, { APIServerID: "4", EncodingVersion: "v1", DecodableVersions: []string{"v1", "v2", "v3alpha1"}, }, }, CommonEncodingVersion: &commonEncodingVersion, }, }, // Columns: Name, CommonEncodingVersion, StorageVersions, Age expected: []metav1.TableRow{{Cells: []interface{}{"agreeWithMore", "v1", "1=v1,2=v1,3=v1 + 1 more...", "0s"}}}, }, } for i, test := range tests { rows, err := printStorageVersion(&test.sv, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintScale(t *testing.T) { tests := []struct { scale autoscaling.Scale options printers.GenerateOptions expected []metav1.TableRow }{ { scale: autoscaling.Scale{ ObjectMeta: metav1.ObjectMeta{ Name: "test-autoscaling", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Spec: autoscaling.ScaleSpec{Replicas: 2}, Status: autoscaling.ScaleStatus{Replicas: 1}, }, expected: []metav1.TableRow{ { Cells: []interface{}{"test-autoscaling", int64(2), int64(1), string("0s")}, }, }, }, } for i, test := range tests { rows, err := printScale(&test.scale, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestTableRowDeepCopyShouldNotPanic(t *testing.T) { tests := []struct { name string printer func() ([]metav1.TableRow, error) }{ { name: "Pod", printer: func() ([]metav1.TableRow, error) { return printPod(&api.Pod{}, printers.GenerateOptions{}) }, }, { name: "PodTemplate", printer: func() ([]metav1.TableRow, error) { return printPodTemplate(&api.PodTemplate{}, printers.GenerateOptions{}) }, }, { name: "PodDisruptionBudget", printer: func() ([]metav1.TableRow, error) { return printPodDisruptionBudget(&policy.PodDisruptionBudget{}, printers.GenerateOptions{}) }, }, { name: "ReplicationController", printer: func() ([]metav1.TableRow, error) { return printReplicationController(&api.ReplicationController{}, printers.GenerateOptions{}) }, }, { name: "ReplicaSet", printer: func() ([]metav1.TableRow, error) { return printReplicaSet(&apps.ReplicaSet{}, printers.GenerateOptions{}) }, }, { name: "Job", printer: func() ([]metav1.TableRow, error) { return printJob(&batch.Job{}, printers.GenerateOptions{}) }, }, { name: "CronJob", printer: func() ([]metav1.TableRow, error) { return printCronJob(&batch.CronJob{}, printers.GenerateOptions{}) }, }, { name: "Service", printer: func() ([]metav1.TableRow, error) { return printService(&api.Service{}, printers.GenerateOptions{}) }, }, { name: "Ingress", printer: func() ([]metav1.TableRow, error) { return printIngress(&networking.Ingress{}, printers.GenerateOptions{}) }, }, { name: "IngressClass", printer: func() ([]metav1.TableRow, error) { return printIngressClass(&networking.IngressClass{}, printers.GenerateOptions{}) }, }, { name: "StatefulSet", printer: func() ([]metav1.TableRow, error) { return printStatefulSet(&apps.StatefulSet{}, printers.GenerateOptions{}) }, }, { name: "DaemonSet", printer: func() ([]metav1.TableRow, error) { return printDaemonSet(&apps.DaemonSet{}, printers.GenerateOptions{}) }, }, { name: "Endpoints", printer: func() ([]metav1.TableRow, error) { return printEndpoints(&api.Endpoints{}, printers.GenerateOptions{}) }, }, { name: "EndpointSlice", printer: func() ([]metav1.TableRow, error) { return printEndpointSlice(&discovery.EndpointSlice{}, printers.GenerateOptions{}) }, }, { name: "CSINode", printer: func() ([]metav1.TableRow, error) { return printCSINode(&storage.CSINode{}, printers.GenerateOptions{}) }, }, { name: "CSIDriver", printer: func() ([]metav1.TableRow, error) { return printCSIDriver(&storage.CSIDriver{}, printers.GenerateOptions{}) }, }, { name: "CSIStorageCapacity", printer: func() ([]metav1.TableRow, error) { return printCSIStorageCapacity(&storage.CSIStorageCapacity{}, printers.GenerateOptions{}) }, }, { name: "MutatingWebhookConfiguration", printer: func() ([]metav1.TableRow, error) { return printMutatingWebhook(&admissionregistration.MutatingWebhookConfiguration{}, printers.GenerateOptions{}) }, }, { name: "ValidatingWebhookConfiguration", printer: func() ([]metav1.TableRow, error) { return printValidatingWebhook(&admissionregistration.ValidatingWebhookConfiguration{}, printers.GenerateOptions{}) }, }, { name: "ValidatingAdmissionPolicy", printer: func() ([]metav1.TableRow, error) { return printValidatingAdmissionPolicy(&admissionregistration.ValidatingAdmissionPolicy{}, printers.GenerateOptions{}) }, }, { name: "ValidatingAdmissionPolicyBinding", printer: func() ([]metav1.TableRow, error) { return printValidatingAdmissionPolicyBinding(&admissionregistration.ValidatingAdmissionPolicyBinding{}, printers.GenerateOptions{}) }, }, { name: "Namespace", printer: func() ([]metav1.TableRow, error) { return printNamespace(&api.Namespace{}, printers.GenerateOptions{}) }, }, { name: "Secret", printer: func() ([]metav1.TableRow, error) { return printSecret(&api.Secret{}, printers.GenerateOptions{}) }, }, { name: "ServiceAccount", printer: func() ([]metav1.TableRow, error) { return printServiceAccount(&api.ServiceAccount{}, printers.GenerateOptions{}) }, }, { name: "Node", printer: func() ([]metav1.TableRow, error) { return printNode(&api.Node{}, printers.GenerateOptions{}) }, }, { name: "PersistentVolume", printer: func() ([]metav1.TableRow, error) { return printPersistentVolume(&api.PersistentVolume{}, printers.GenerateOptions{}) }, }, { name: "PersistentVolumeClaim", printer: func() ([]metav1.TableRow, error) { return printPersistentVolumeClaim(&api.PersistentVolumeClaim{}, printers.GenerateOptions{}) }, }, { name: "Event", printer: func() ([]metav1.TableRow, error) { return printEvent(&api.Event{}, printers.GenerateOptions{}) }, }, { name: "RoleBinding", printer: func() ([]metav1.TableRow, error) { return printRoleBinding(&rbac.RoleBinding{}, printers.GenerateOptions{}) }, }, { name: "ClusterRoleBinding", printer: func() ([]metav1.TableRow, error) { return printClusterRoleBinding(&rbac.ClusterRoleBinding{}, printers.GenerateOptions{}) }, }, { name: "CertificateSigningRequest", printer: func() ([]metav1.TableRow, error) { return printCertificateSigningRequest(&certificates.CertificateSigningRequest{}, printers.GenerateOptions{}) }, }, { name: "ComponentStatus", printer: func() ([]metav1.TableRow, error) { return printComponentStatus(&api.ComponentStatus{}, printers.GenerateOptions{}) }, }, { name: "Deployment", printer: func() ([]metav1.TableRow, error) { return printDeployment(&apps.Deployment{}, printers.GenerateOptions{}) }, }, { name: "HorizontalPodAutoscaler", printer: func() ([]metav1.TableRow, error) { return printHorizontalPodAutoscaler(&autoscaling.HorizontalPodAutoscaler{}, printers.GenerateOptions{}) }, }, { name: "ConfigMap", printer: func() ([]metav1.TableRow, error) { return printConfigMap(&api.ConfigMap{}, printers.GenerateOptions{}) }, }, { name: "NetworkPolicy", printer: func() ([]metav1.TableRow, error) { return printNetworkPolicy(&networking.NetworkPolicy{}, printers.GenerateOptions{}) }, }, { name: "StorageClass", printer: func() ([]metav1.TableRow, error) { return printStorageClass(&storage.StorageClass{}, printers.GenerateOptions{}) }, }, { name: "Lease", printer: func() ([]metav1.TableRow, error) { return printLease(&coordination.Lease{}, printers.GenerateOptions{}) }, }, { name: "ControllerRevision", printer: func() ([]metav1.TableRow, error) { return printControllerRevision(&apps.ControllerRevision{}, printers.GenerateOptions{}) }, }, { name: "ResourceQuota", printer: func() ([]metav1.TableRow, error) { return printResourceQuota(&api.ResourceQuota{}, printers.GenerateOptions{}) }, }, { name: "PriorityClass", printer: func() ([]metav1.TableRow, error) { return printPriorityClass(&scheduling.PriorityClass{}, printers.GenerateOptions{}) }, }, { name: "RuntimeClass", printer: func() ([]metav1.TableRow, error) { return printRuntimeClass(&nodeapi.RuntimeClass{}, printers.GenerateOptions{}) }, }, { name: "VolumeAttachment", printer: func() ([]metav1.TableRow, error) { return printVolumeAttachment(&storage.VolumeAttachment{}, printers.GenerateOptions{}) }, }, { name: "FlowSchema", printer: func() ([]metav1.TableRow, error) { return printFlowSchema(&flowcontrol.FlowSchema{}, printers.GenerateOptions{}) }, }, { name: "StorageVersion", printer: func() ([]metav1.TableRow, error) { return printStorageVersion(&apiserverinternal.StorageVersion{}, printers.GenerateOptions{}) }, }, { name: "PriorityLevelConfiguration", printer: func() ([]metav1.TableRow, error) { return printPriorityLevelConfiguration(&flowcontrol.PriorityLevelConfiguration{}, printers.GenerateOptions{}) }, }, { name: "Scale", printer: func() ([]metav1.TableRow, error) { return printScale(&autoscaling.Scale{}, printers.GenerateOptions{}) }, }, { name: "Status", printer: func() ([]metav1.TableRow, error) { return printStatus(&metav1.Status{}, printers.GenerateOptions{}) }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { rows, err := test.printer() if err != nil { t.Fatalf("expected no error, but got: %#v", err) } if len(rows) <= 0 { t.Fatalf("expected to have at least one TableRow, but got: %d", len(rows)) } func() { defer func() { if err := recover(); err != nil { // Same as stdlib http server code. Manually allocate stack // trace buffer size to prevent excessively large logs const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] err = fmt.Errorf("%q stack:\n%s", err, buf) t.Errorf("Expected no panic, but got: %v", err) } }() // should not panic rows[0].DeepCopy() }() }) } } func TestPrintIPAddress(t *testing.T) { ip := networking.IPAddress{ ObjectMeta: metav1.ObjectMeta{ Name: "192.168.2.2", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, Spec: networking.IPAddressSpec{ ParentRef: &networking.ParentReference{ Group: "mygroup", Resource: "myresource", Namespace: "mynamespace", Name: "myname", }, }, } // Columns: Name, ParentRef, Age expected := []metav1.TableRow{{Cells: []interface{}{"192.168.2.2", "myresource.mygroup/mynamespace/myname", "10y"}}} rows, err := printIPAddress(&ip, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error generating table rows for IPAddress: %#v", err) } rows[0].Object.Object = nil if !reflect.DeepEqual(expected, rows) { t.Errorf("mismatch: %s", cmp.Diff(expected, rows)) } } func TestPrintIPAddressList(t *testing.T) { ipList := networking.IPAddressList{ Items: []networking.IPAddress{ { ObjectMeta: metav1.ObjectMeta{ Name: "192.168.2.2", CreationTimestamp: metav1.Time{}, }, Spec: networking.IPAddressSpec{ ParentRef: &networking.ParentReference{ Group: "mygroup", Resource: "myresource", Namespace: "mynamespace", Name: "myname", }, }, }, { ObjectMeta: metav1.ObjectMeta{ Name: "2001:db8::2", CreationTimestamp: metav1.Time{}, }, Spec: networking.IPAddressSpec{ ParentRef: &networking.ParentReference{ Group: "mygroup2", Resource: "myresource2", Namespace: "mynamespace2", Name: "myname2", }, }, }, }, } // Columns: Name, ParentRef, Age expected := []metav1.TableRow{ {Cells: []interface{}{"192.168.2.2", "myresource.mygroup/mynamespace/myname", ""}}, {Cells: []interface{}{"2001:db8::2", "myresource2.mygroup2/mynamespace2/myname2", ""}}, } rows, err := printIPAddressList(&ipList, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error generating table rows for IPAddress: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(expected, rows) { t.Errorf("mismatch: %s", cmp.Diff(expected, rows)) } } func TestPrintServiceCIDR(t *testing.T) { ipv4CIDR := "10.1.0.0/16" ipv6CIDR := "fd00:1:1::/64" tests := []struct { ccc networking.ServiceCIDR options printers.GenerateOptions expected []metav1.TableRow }{ { // Test name, IPv4 only. ccc: networking.ServiceCIDR{ ObjectMeta: metav1.ObjectMeta{Name: "test1"}, Spec: networking.ServiceCIDRSpec{ CIDRs: []string{ipv4CIDR}, }, }, options: printers.GenerateOptions{}, // Columns: Name, IPv4, IPv6, Age. expected: []metav1.TableRow{{Cells: []interface{}{"test1", ipv4CIDR, ""}}}, }, { // Test name, IPv6 only. ccc: networking.ServiceCIDR{ ObjectMeta: metav1.ObjectMeta{Name: "test5"}, Spec: networking.ServiceCIDRSpec{ CIDRs: []string{ipv6CIDR}, }, }, options: printers.GenerateOptions{}, // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age expected: []metav1.TableRow{{Cells: []interface{}{"test5", ipv6CIDR, ""}}}, }, { // Test name, DualStack. ccc: networking.ServiceCIDR{ ObjectMeta: metav1.ObjectMeta{Name: "test9"}, Spec: networking.ServiceCIDRSpec{ CIDRs: []string{ipv4CIDR, ipv6CIDR}, }, }, options: printers.GenerateOptions{}, // Columns: Name, PerNodeHostBits, IPv4, IPv6, Age. expected: []metav1.TableRow{{Cells: []interface{}{"test9", ipv4CIDR + "," + ipv6CIDR, ""}}}, }, } for i, test := range tests { rows, err := printServiceCIDR(&test.ccc, test.options) if err != nil { t.Fatal(err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows)) } } } func TestPrintServiceCIDRList(t *testing.T) { cccList := networking.ServiceCIDRList{ Items: []networking.ServiceCIDR{ { ObjectMeta: metav1.ObjectMeta{Name: "ccc1"}, Spec: networking.ServiceCIDRSpec{ CIDRs: []string{"10.1.0.0/16", "fd00:1:1::/64"}, }, }, { ObjectMeta: metav1.ObjectMeta{Name: "ccc2"}, Spec: networking.ServiceCIDRSpec{ CIDRs: []string{"10.2.0.0/16", "fd00:2:1::/64"}, }, }, }, } tests := []struct { options printers.GenerateOptions expected []metav1.TableRow }{ { // Test name, DualStack with node selector, wide. options: printers.GenerateOptions{Wide: false}, expected: []metav1.TableRow{ // Columns: Name, IPv4, IPv6, Age. {Cells: []interface{}{"ccc1", "10.1.0.0/16,fd00:1:1::/64", ""}}, {Cells: []interface{}{"ccc2", "10.2.0.0/16,fd00:2:1::/64", ""}}, }, }, { // Test name, DualStack with node selector, wide. options: printers.GenerateOptions{Wide: true}, expected: []metav1.TableRow{ // Columns: Name, CIDRs, Age. {Cells: []interface{}{"ccc1", "10.1.0.0/16,fd00:1:1::/64", ""}}, {Cells: []interface{}{"ccc2", "10.2.0.0/16,fd00:2:1::/64", ""}}, }, }, } for _, test := range tests { rows, err := printServiceCIDRList(&cccList, test.options) if err != nil { t.Fatalf("Error printing service list: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { t.Errorf("mismatch: %s", cmp.Diff(test.expected, rows)) } } } func TestPrintStorageVersionMigration(t *testing.T) { storageVersionMigration := storagemigration.StorageVersionMigration{ TypeMeta: metav1.TypeMeta{ Kind: "StorageVersionMigration", APIVersion: "storagemigration.k8s.io/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "print-test", }, Spec: storagemigration.StorageVersionMigrationSpec{ Resource: storagemigration.GroupVersionResource{ Group: "test-group", Version: "test-version", Resource: "test-resource", }, }, } // Columns: Name, GVRTOMIGRATE expected := []metav1.TableRow{{Cells: []interface{}{"print-test", "test-resource.test-version.test-group"}}} rows, err := printStorageVersionMigration(&storageVersionMigration, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error generating table rows for StorageVersionMigration: %#v", err) } rows[0].Object.Object = nil if !reflect.DeepEqual(expected, rows) { t.Errorf("mismatch: %s", cmp.Diff(expected, rows)) } } func TestPrintStorageVersionMigrationList(t *testing.T) { storageVersionMigrationList := storagemigration.StorageVersionMigrationList{ Items: []storagemigration.StorageVersionMigration{ { TypeMeta: metav1.TypeMeta{ Kind: "StorageVersionMigration", APIVersion: "storagemigration.k8s.io/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "print-test", }, Spec: storagemigration.StorageVersionMigrationSpec{ Resource: storagemigration.GroupVersionResource{ Group: "test-group", Version: "test-version", Resource: "test-resource", }, }, }, { TypeMeta: metav1.TypeMeta{ Kind: "StorageVersionMigration", APIVersion: "storagemigration.k8s.io/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: "print-test2", }, Spec: storagemigration.StorageVersionMigrationSpec{ Resource: storagemigration.GroupVersionResource{ Group: "test-group2", Version: "test-version2", Resource: "test-resource2", }, }, }, }, } // Columns: Name, GVRTOMIGRATE expected := []metav1.TableRow{ {Cells: []interface{}{"print-test", "test-resource.test-version.test-group"}}, {Cells: []interface{}{"print-test2", "test-resource2.test-version2.test-group2"}}, } rows, err := printStorageVersionMigrationList(&storageVersionMigrationList, printers.GenerateOptions{}) if err != nil { t.Fatalf("Error generating table rows for StorageVersionMigration: %#v", err) } for i := range rows { rows[i].Object.Object = nil } if !reflect.DeepEqual(expected, rows) { t.Errorf("mismatch: %s", cmp.Diff(expected, rows)) } }