1483 lines
44 KiB
Go
1483 lines
44 KiB
Go
/*
|
|
Copyright 2021 The Karmada 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 lifted
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/util/diff"
|
|
|
|
"github.com/karmada-io/karmada/pkg/printers"
|
|
)
|
|
|
|
// MockPrintHandler is a mock implementation of printers.PrintHandler
|
|
type MockPrintHandler struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockPrintHandler) TableHandler(columnDefinitions []metav1.TableColumnDefinition, printFunc interface{}) error {
|
|
args := m.Called(columnDefinitions, printFunc)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func TestAddCoreV1Handlers(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
expectedCalls int
|
|
columnChecks map[string][]string
|
|
printFuncTypes map[string]reflect.Type
|
|
}{
|
|
{
|
|
name: "Verify handlers are added correctly",
|
|
expectedCalls: 4,
|
|
columnChecks: map[string][]string{
|
|
"Pod": {"Name", "Ready", "Status", "Restarts", "Age", "IP", "Node", "Nominated Node", "Readiness Gates"},
|
|
"Node": {"Name", "Status", "Roles", "Age", "Version", "Internal-IP", "External-IP", "OS-Image", "Kernel-Version", "Container-Runtime"},
|
|
},
|
|
printFuncTypes: map[string]reflect.Type{
|
|
"PodList": reflect.TypeOf(func(*corev1.PodList, printers.GenerateOptions) ([]metav1.TableRow, error) { return nil, nil }),
|
|
"Pod": reflect.TypeOf(func(*corev1.Pod, printers.GenerateOptions) ([]metav1.TableRow, error) { return nil, nil }),
|
|
"Node": reflect.TypeOf(func(*corev1.Node, printers.GenerateOptions) ([]metav1.TableRow, error) { return nil, nil }),
|
|
"NodeList": reflect.TypeOf(func(*corev1.NodeList, printers.GenerateOptions) ([]metav1.TableRow, error) { return nil, nil }),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
mockHandler := &MockPrintHandler{}
|
|
mockHandler.On("TableHandler", mock.Anything, mock.Anything).Return(nil)
|
|
|
|
AddCoreV1Handlers(mockHandler)
|
|
|
|
assert.Equal(t, tc.expectedCalls, len(mockHandler.Calls))
|
|
|
|
for i, call := range mockHandler.Calls {
|
|
columnDefinitions := call.Arguments[0].([]metav1.TableColumnDefinition)
|
|
printFunc := call.Arguments[1]
|
|
|
|
resourceType := ""
|
|
switch i {
|
|
case 0:
|
|
resourceType = "PodList"
|
|
case 1:
|
|
resourceType = "Pod"
|
|
case 2:
|
|
resourceType = "Node"
|
|
case 3:
|
|
resourceType = "NodeList"
|
|
}
|
|
|
|
// Check column definitions
|
|
if expectedColumns, ok := tc.columnChecks[strings.TrimSuffix(resourceType, "List")]; ok {
|
|
assert.Equal(t, len(expectedColumns), len(columnDefinitions))
|
|
for j, name := range expectedColumns {
|
|
assert.Equal(t, name, columnDefinitions[j].Name)
|
|
assert.NotEmpty(t, columnDefinitions[j].Type)
|
|
assert.NotEmpty(t, columnDefinitions[j].Description)
|
|
}
|
|
}
|
|
|
|
// Check print function type
|
|
if expectedType, ok := tc.printFuncTypes[resourceType]; ok {
|
|
assert.Equal(t, expectedType, reflect.TypeOf(printFunc))
|
|
}
|
|
}
|
|
|
|
mockHandler.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrintNode(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
node *corev1.Node
|
|
options printers.GenerateOptions
|
|
expectedChecks func(*testing.T, []metav1.TableRow)
|
|
}{
|
|
{
|
|
name: "Basic ready node",
|
|
node: &corev1.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node1",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-24 * time.Hour)},
|
|
},
|
|
Status: corev1.NodeStatus{
|
|
Conditions: []corev1.NodeCondition{
|
|
{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
|
|
},
|
|
NodeInfo: corev1.NodeSystemInfo{
|
|
KubeletVersion: "v1.20.0",
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Len(t, rows[0].Cells, 5)
|
|
assert.Equal(t, "node1", rows[0].Cells[0])
|
|
assert.Contains(t, rows[0].Cells[1], "Ready")
|
|
assert.NotEmpty(t, rows[0].Cells[3]) // Only check it's not empty due to time dependency
|
|
assert.Equal(t, "v1.20.0", rows[0].Cells[4])
|
|
},
|
|
},
|
|
{
|
|
name: "Node with roles",
|
|
node: &corev1.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node2",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-48 * time.Hour)},
|
|
Labels: map[string]string{
|
|
"node-role.kubernetes.io/control-plane": "",
|
|
"node-role.kubernetes.io/worker": "",
|
|
},
|
|
},
|
|
Status: corev1.NodeStatus{
|
|
Conditions: []corev1.NodeCondition{
|
|
{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
|
|
},
|
|
NodeInfo: corev1.NodeSystemInfo{
|
|
KubeletVersion: "v1.21.0",
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Len(t, rows[0].Cells, 5)
|
|
assert.Equal(t, "node2", rows[0].Cells[0])
|
|
assert.Contains(t, rows[0].Cells[1], "Ready")
|
|
// Use Contains for roles as the order might not be guaranteed
|
|
assert.Contains(t, rows[0].Cells[2], "control-plane")
|
|
assert.Contains(t, rows[0].Cells[2], "worker")
|
|
assert.NotEmpty(t, rows[0].Cells[3])
|
|
assert.Equal(t, "v1.21.0", rows[0].Cells[4])
|
|
},
|
|
},
|
|
{
|
|
name: "Unschedulable node",
|
|
node: &corev1.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node3",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-72 * time.Hour)},
|
|
},
|
|
Spec: corev1.NodeSpec{
|
|
Unschedulable: true,
|
|
},
|
|
Status: corev1.NodeStatus{
|
|
Conditions: []corev1.NodeCondition{
|
|
{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
|
|
},
|
|
NodeInfo: corev1.NodeSystemInfo{
|
|
KubeletVersion: "v1.22.0",
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Len(t, rows[0].Cells, 5)
|
|
assert.Equal(t, "node3", rows[0].Cells[0])
|
|
assert.Contains(t, rows[0].Cells[1], "SchedulingDisabled")
|
|
assert.NotEmpty(t, rows[0].Cells[3])
|
|
assert.Equal(t, "v1.22.0", rows[0].Cells[4])
|
|
},
|
|
},
|
|
{
|
|
name: "Node with missing OS, kernel, and container runtime info",
|
|
node: &corev1.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node6",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-144 * time.Hour)},
|
|
},
|
|
Status: corev1.NodeStatus{
|
|
Conditions: []corev1.NodeCondition{
|
|
{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
|
|
},
|
|
NodeInfo: corev1.NodeSystemInfo{
|
|
KubeletVersion: "v1.25.0",
|
|
// OSImage, KernelVersion, and ContainerRuntimeVersion are intentionally left empty
|
|
},
|
|
Addresses: []corev1.NodeAddress{
|
|
{Type: corev1.NodeInternalIP, Address: "192.168.1.2"},
|
|
{Type: corev1.NodeExternalIP, Address: "203.0.113.2"},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{Wide: true},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Len(t, rows[0].Cells, 10) // Wide option should produce 10 columns
|
|
assert.Equal(t, "node6", rows[0].Cells[0])
|
|
assert.Contains(t, rows[0].Cells[1], "Ready")
|
|
assert.NotEmpty(t, rows[0].Cells[3])
|
|
assert.Equal(t, "v1.25.0", rows[0].Cells[4])
|
|
assert.Equal(t, "192.168.1.2", rows[0].Cells[5]) // Internal IP
|
|
assert.Equal(t, "203.0.113.2", rows[0].Cells[6]) // External IP
|
|
assert.Equal(t, "<unknown>", rows[0].Cells[7]) // OSImage
|
|
assert.Equal(t, "<unknown>", rows[0].Cells[8]) // KernelVersion
|
|
assert.Equal(t, "<unknown>", rows[0].Cells[9]) // ContainerRuntimeVersion
|
|
},
|
|
},
|
|
{
|
|
name: "Node with wide option",
|
|
node: &corev1.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node4",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-96 * time.Hour)},
|
|
},
|
|
Status: corev1.NodeStatus{
|
|
Conditions: []corev1.NodeCondition{
|
|
{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
|
|
},
|
|
NodeInfo: corev1.NodeSystemInfo{
|
|
KubeletVersion: "v1.23.0",
|
|
OSImage: "Ubuntu 20.04",
|
|
KernelVersion: "5.4.0-42-generic",
|
|
ContainerRuntimeVersion: "docker://19.03.8",
|
|
},
|
|
Addresses: []corev1.NodeAddress{
|
|
{Type: corev1.NodeInternalIP, Address: "192.168.1.1"},
|
|
{Type: corev1.NodeExternalIP, Address: "203.0.113.1"},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{Wide: true},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Len(t, rows[0].Cells, 10) // Wide option should produce 10 columns
|
|
assert.Equal(t, "node4", rows[0].Cells[0])
|
|
assert.Contains(t, rows[0].Cells[1], "Ready")
|
|
assert.NotEmpty(t, rows[0].Cells[3])
|
|
assert.Equal(t, "v1.23.0", rows[0].Cells[4])
|
|
assert.Equal(t, "192.168.1.1", rows[0].Cells[5]) // Internal IP
|
|
assert.Equal(t, "203.0.113.1", rows[0].Cells[6]) // External IP
|
|
assert.Equal(t, "Ubuntu 20.04", rows[0].Cells[7])
|
|
assert.Equal(t, "5.4.0-42-generic", rows[0].Cells[8])
|
|
assert.Equal(t, "docker://19.03.8", rows[0].Cells[9])
|
|
},
|
|
},
|
|
{
|
|
name: "Node with no conditions",
|
|
node: &corev1.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node5",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-120 * time.Hour)},
|
|
},
|
|
Status: corev1.NodeStatus{
|
|
NodeInfo: corev1.NodeSystemInfo{
|
|
KubeletVersion: "v1.24.0",
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Len(t, rows[0].Cells, 5)
|
|
assert.Equal(t, "node5", rows[0].Cells[0])
|
|
assert.Equal(t, "Unknown", rows[0].Cells[1])
|
|
assert.Equal(t, "<none>", rows[0].Cells[2])
|
|
assert.NotEmpty(t, rows[0].Cells[3])
|
|
assert.Equal(t, "v1.24.0", rows[0].Cells[4])
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
rows, err := printNode(tc.node, tc.options)
|
|
assert.NoError(t, err)
|
|
|
|
// Run the custom checks for this test case
|
|
tc.expectedChecks(t, rows)
|
|
|
|
// Ensure the original node object is included in the output
|
|
assert.Equal(t, runtime.RawExtension{Object: tc.node}, rows[0].Object)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetNodeExternalIP(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
node *corev1.Node
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Node with external IP",
|
|
node: &corev1.Node{
|
|
Status: corev1.NodeStatus{
|
|
Addresses: []corev1.NodeAddress{
|
|
{Type: corev1.NodeExternalIP, Address: "203.0.113.1"},
|
|
{Type: corev1.NodeInternalIP, Address: "192.168.1.1"},
|
|
},
|
|
},
|
|
},
|
|
expected: "203.0.113.1",
|
|
},
|
|
{
|
|
name: "Node without external IP",
|
|
node: &corev1.Node{
|
|
Status: corev1.NodeStatus{
|
|
Addresses: []corev1.NodeAddress{
|
|
{Type: corev1.NodeInternalIP, Address: "192.168.1.1"},
|
|
},
|
|
},
|
|
},
|
|
expected: "<none>",
|
|
},
|
|
{
|
|
name: "Node with no addresses",
|
|
node: &corev1.Node{},
|
|
expected: "<none>",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := getNodeExternalIP(tt.node)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetNodeInternalIP(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
node *corev1.Node
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Node with internal IP",
|
|
node: &corev1.Node{
|
|
Status: corev1.NodeStatus{
|
|
Addresses: []corev1.NodeAddress{
|
|
{Type: corev1.NodeExternalIP, Address: "203.0.113.1"},
|
|
{Type: corev1.NodeInternalIP, Address: "192.168.1.1"},
|
|
},
|
|
},
|
|
},
|
|
expected: "192.168.1.1",
|
|
},
|
|
{
|
|
name: "Node without internal IP",
|
|
node: &corev1.Node{
|
|
Status: corev1.NodeStatus{
|
|
Addresses: []corev1.NodeAddress{
|
|
{Type: corev1.NodeExternalIP, Address: "203.0.113.1"},
|
|
},
|
|
},
|
|
},
|
|
expected: "<none>",
|
|
},
|
|
{
|
|
name: "Node with no addresses",
|
|
node: &corev1.Node{},
|
|
expected: "<none>",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := getNodeInternalIP(tt.node)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFindNodeRoles(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
node *corev1.Node
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "Node with single role",
|
|
node: &corev1.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"node-role.kubernetes.io/control-plane": "",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{"control-plane"},
|
|
},
|
|
{
|
|
name: "Node with multiple roles",
|
|
node: &corev1.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"node-role.kubernetes.io/control-plane": "",
|
|
"node-role.kubernetes.io/worker": "",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{"control-plane", "worker"},
|
|
},
|
|
{
|
|
name: "Node with kubernetes.io/role label",
|
|
node: &corev1.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"kubernetes.io/role": "special",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{"special"},
|
|
},
|
|
{
|
|
name: "Node with no role labels",
|
|
node: &corev1.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "Node with special characters in role names",
|
|
node: &corev1.Node{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"node-role.kubernetes.io/role-with-hyphen": "",
|
|
"node-role.kubernetes.io/role_with_underscore": "",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{"role-with-hyphen", "role_with_underscore"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := findNodeRoles(tt.node)
|
|
assert.ElementsMatch(t, tt.expected, result) // Order of roles can not be determined
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrintNodeList(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
nodeList *corev1.NodeList
|
|
options printers.GenerateOptions
|
|
expected func(*testing.T, []metav1.TableRow)
|
|
}{
|
|
{
|
|
name: "Empty node list",
|
|
nodeList: &corev1.NodeList{},
|
|
options: printers.GenerateOptions{},
|
|
expected: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Empty(t, rows)
|
|
},
|
|
},
|
|
{
|
|
name: "Single node",
|
|
nodeList: &corev1.NodeList{
|
|
Items: []corev1.Node{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node1",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-24 * time.Hour)},
|
|
},
|
|
Status: corev1.NodeStatus{
|
|
Conditions: []corev1.NodeCondition{
|
|
{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
|
|
},
|
|
NodeInfo: corev1.NodeSystemInfo{
|
|
KubeletVersion: "v1.20.0",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expected: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "node1", rows[0].Cells[0])
|
|
assert.Contains(t, rows[0].Cells[1], "Ready")
|
|
assert.NotEmpty(t, rows[0].Cells[3])
|
|
assert.Equal(t, "v1.20.0", rows[0].Cells[4])
|
|
},
|
|
},
|
|
{
|
|
name: "Multiple nodes with different states",
|
|
nodeList: &corev1.NodeList{
|
|
Items: []corev1.Node{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node1",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-24 * time.Hour)},
|
|
},
|
|
Status: corev1.NodeStatus{
|
|
Conditions: []corev1.NodeCondition{
|
|
{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
|
|
},
|
|
NodeInfo: corev1.NodeSystemInfo{
|
|
KubeletVersion: "v1.20.0",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node2",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-48 * time.Hour)},
|
|
},
|
|
Spec: corev1.NodeSpec{
|
|
Unschedulable: true,
|
|
},
|
|
Status: corev1.NodeStatus{
|
|
Conditions: []corev1.NodeCondition{
|
|
{Type: corev1.NodeReady, Status: corev1.ConditionFalse},
|
|
},
|
|
NodeInfo: corev1.NodeSystemInfo{
|
|
KubeletVersion: "v1.21.0",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expected: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 2)
|
|
assert.Equal(t, "node1", rows[0].Cells[0])
|
|
assert.Contains(t, rows[0].Cells[1], "Ready")
|
|
assert.Equal(t, "v1.20.0", rows[0].Cells[4])
|
|
assert.Equal(t, "node2", rows[1].Cells[0])
|
|
assert.Contains(t, rows[1].Cells[1], "NotReady")
|
|
assert.Contains(t, rows[1].Cells[1], "SchedulingDisabled")
|
|
assert.Equal(t, "v1.21.0", rows[1].Cells[4])
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
rows, err := printNodeList(tc.nodeList, tc.options)
|
|
assert.NoError(t, err)
|
|
tc.expected(t, rows)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrintPodList(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
podList *corev1.PodList
|
|
options printers.GenerateOptions
|
|
expected func(*testing.T, []metav1.TableRow)
|
|
}{
|
|
{
|
|
name: "Empty pod list",
|
|
podList: &corev1.PodList{},
|
|
options: printers.GenerateOptions{},
|
|
expected: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Empty(t, rows)
|
|
},
|
|
},
|
|
{
|
|
name: "Single running pod",
|
|
podList: &corev1.PodList{
|
|
Items: []corev1.Pod{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod1",
|
|
Namespace: "default",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{Name: "container1"},
|
|
},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Ready: true,
|
|
State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expected: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "pod1", rows[0].Cells[0])
|
|
assert.Equal(t, "1/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Running", rows[0].Cells[2])
|
|
assert.NotEmpty(t, rows[0].Cells[4])
|
|
},
|
|
},
|
|
{
|
|
name: "Multiple pods with different states",
|
|
podList: &corev1.PodList{
|
|
Items: []corev1.Pod{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod1",
|
|
Namespace: "default",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{Name: "container1"},
|
|
},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Ready: true,
|
|
State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pod2",
|
|
Namespace: "kube-system",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-2 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{Name: "container2"},
|
|
},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodPending,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Ready: false,
|
|
State: corev1.ContainerState{Waiting: &corev1.ContainerStateWaiting{Reason: "ContainerCreating"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expected: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 2)
|
|
assert.Equal(t, "pod1", rows[0].Cells[0])
|
|
assert.Equal(t, "1/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Running", rows[0].Cells[2])
|
|
assert.NotEmpty(t, rows[0].Cells[4])
|
|
assert.Equal(t, "pod2", rows[1].Cells[0])
|
|
assert.Equal(t, "0/1", rows[1].Cells[1])
|
|
assert.Contains(t, []string{"Pending", "ContainerCreating"}, rows[1].Cells[2])
|
|
assert.NotEmpty(t, rows[1].Cells[4])
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
rows, err := printPodList(tc.podList, tc.options)
|
|
assert.NoError(t, err)
|
|
tc.expected(t, rows)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrintPod(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
pod *corev1.Pod
|
|
options printers.GenerateOptions
|
|
expectedChecks func(*testing.T, []metav1.TableRow)
|
|
}{
|
|
{
|
|
name: "Running pod",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "running-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{Ready: true, State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}}},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Len(t, rows[0].Cells, 5)
|
|
assert.Equal(t, "running-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "1/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Running", rows[0].Cells[2])
|
|
assert.Equal(t, int64(0), rows[0].Cells[3])
|
|
assert.Regexp(t, `\d+[hm]`, rows[0].Cells[4])
|
|
},
|
|
},
|
|
{
|
|
name: "Pending pod with ContainerCreating",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "pending-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-30 * time.Minute)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodPending,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{Ready: false, State: corev1.ContainerState{Waiting: &corev1.ContainerStateWaiting{Reason: "ContainerCreating"}}},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Len(t, rows[0].Cells, 5)
|
|
assert.Equal(t, "pending-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "ContainerCreating", rows[0].Cells[2])
|
|
assert.Equal(t, int64(0), rows[0].Cells[3])
|
|
assert.Regexp(t, `\d+m`, rows[0].Cells[4])
|
|
},
|
|
},
|
|
{
|
|
name: "Succeeded pod",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "succeeded-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-3 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodSucceeded,
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "succeeded-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Succeeded", rows[0].Cells[2])
|
|
assert.Equal(t, int64(0), rows[0].Cells[3])
|
|
assert.Regexp(t, `\d+h`, rows[0].Cells[4])
|
|
assert.Equal(t, podSuccessConditions, rows[0].Conditions)
|
|
},
|
|
},
|
|
{
|
|
name: "Failed pod",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "failed-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-2 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodFailed,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{Ready: false, State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{Reason: "Error"}}},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "failed-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Error", rows[0].Cells[2])
|
|
assert.Equal(t, int64(0), rows[0].Cells[3])
|
|
assert.Regexp(t, `(\d+h|\d+m)`, rows[0].Cells[4]) // Match either hours or minutes
|
|
assert.Equal(t, podFailedConditions, rows[0].Conditions)
|
|
},
|
|
},
|
|
{
|
|
name: "Pod with multiple containers and restarts",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "multi-container-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-3 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}, {Name: "container2"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{Ready: true, RestartCount: 2, State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}}},
|
|
{Ready: true, RestartCount: 1, State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}}},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Len(t, rows[0].Cells, 5)
|
|
assert.Equal(t, "multi-container-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "2/2", rows[0].Cells[1])
|
|
assert.Equal(t, "Running", rows[0].Cells[2])
|
|
assert.Equal(t, int64(3), rows[0].Cells[3])
|
|
assert.Regexp(t, `\d+h`, rows[0].Cells[4])
|
|
},
|
|
},
|
|
{
|
|
name: "Pod with readiness gates",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "readiness-gate-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-4 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}},
|
|
ReadinessGates: []corev1.PodReadinessGate{
|
|
{ConditionType: "custom-condition"},
|
|
},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{Ready: true, State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}}},
|
|
},
|
|
Conditions: []corev1.PodCondition{
|
|
{Type: "custom-condition", Status: corev1.ConditionTrue},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{Wide: true},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Len(t, rows[0].Cells, 9)
|
|
assert.Equal(t, "readiness-gate-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "1/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Running", rows[0].Cells[2])
|
|
assert.Equal(t, int64(0), rows[0].Cells[3])
|
|
assert.Regexp(t, `\d+h`, rows[0].Cells[4])
|
|
assert.Equal(t, "<none>", rows[0].Cells[5]) // IP
|
|
assert.Equal(t, "<none>", rows[0].Cells[6]) // Node
|
|
assert.Equal(t, "<none>", rows[0].Cells[7]) // Nominated Node
|
|
assert.Equal(t, "1/1", rows[0].Cells[8]) // Readiness Gates
|
|
},
|
|
},
|
|
{
|
|
name: "Pod with init container - waiting",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "init-pod-waiting",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
InitContainers: []corev1.Container{{Name: "init-container"}},
|
|
Containers: []corev1.Container{{Name: "main-container"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodPending,
|
|
InitContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "init-container",
|
|
Ready: false,
|
|
State: corev1.ContainerState{Waiting: &corev1.ContainerStateWaiting{Reason: "ContainerCreating"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Len(t, rows[0].Cells, 5)
|
|
assert.Equal(t, "init-pod-waiting", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Init:ContainerCreating", rows[0].Cells[2])
|
|
assert.Equal(t, int64(0), rows[0].Cells[3])
|
|
assert.Regexp(t, `\d+[hm]`, rows[0].Cells[4])
|
|
},
|
|
},
|
|
{
|
|
name: "Terminating pod",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "terminating-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-6 * time.Hour)},
|
|
DeletionTimestamp: &metav1.Time{Time: time.Now().Add(-5 * time.Minute)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{Ready: true, State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}}},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Len(t, rows[0].Cells, 5)
|
|
assert.Equal(t, "terminating-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "1/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Terminating", rows[0].Cells[2])
|
|
assert.Equal(t, int64(0), rows[0].Cells[3])
|
|
assert.Regexp(t, `\d+h`, rows[0].Cells[4])
|
|
},
|
|
},
|
|
{
|
|
name: "Node unreachable pod",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "unreachable-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-7 * time.Hour)},
|
|
DeletionTimestamp: &metav1.Time{Time: time.Now().Add(-10 * time.Minute)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
Reason: NodeUnreachablePodReason,
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "unreachable-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Unknown", rows[0].Cells[2])
|
|
assert.Equal(t, int64(0), rows[0].Cells[3])
|
|
assert.Regexp(t, `\d+h`, rows[0].Cells[4])
|
|
},
|
|
},
|
|
{
|
|
name: "Pod with init container terminated with signal",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "init-signal-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-30 * time.Minute)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
InitContainers: []corev1.Container{{Name: "init-container"}},
|
|
Containers: []corev1.Container{{Name: "main-container"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodPending,
|
|
InitContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "init-container",
|
|
State: corev1.ContainerState{
|
|
Terminated: &corev1.ContainerStateTerminated{
|
|
Signal: 9,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "init-signal-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Pending", rows[0].Cells[2])
|
|
},
|
|
},
|
|
{
|
|
name: "Pod with init container terminated with exit code",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "init-exit-code-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-35 * time.Minute)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
InitContainers: []corev1.Container{{Name: "init-container"}},
|
|
Containers: []corev1.Container{{Name: "main-container"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodPending,
|
|
InitContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "init-container",
|
|
State: corev1.ContainerState{
|
|
Terminated: &corev1.ContainerStateTerminated{
|
|
ExitCode: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "init-exit-code-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Init:ExitCode:1", rows[0].Cells[2])
|
|
},
|
|
},
|
|
{
|
|
name: "Pod with init container terminated with reason",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "init-reason-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-40 * time.Minute)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
InitContainers: []corev1.Container{{Name: "init-container"}},
|
|
Containers: []corev1.Container{{Name: "main-container"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodPending,
|
|
InitContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "init-container",
|
|
State: corev1.ContainerState{
|
|
Terminated: &corev1.ContainerStateTerminated{
|
|
Reason: "Error",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "init-reason-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Pending", rows[0].Cells[2])
|
|
},
|
|
},
|
|
{
|
|
name: "Pod with multiple init containers, some pending",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "multi-init-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-45 * time.Minute)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
InitContainers: []corev1.Container{
|
|
{Name: "init-container-1"},
|
|
{Name: "init-container-2"},
|
|
{Name: "init-container-3"},
|
|
},
|
|
Containers: []corev1.Container{{Name: "main-container"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodPending,
|
|
InitContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "init-container-1",
|
|
Ready: true,
|
|
State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{ExitCode: 0}},
|
|
},
|
|
{
|
|
Name: "init-container-2",
|
|
Ready: false,
|
|
State: corev1.ContainerState{Waiting: &corev1.ContainerStateWaiting{}},
|
|
},
|
|
{
|
|
Name: "init-container-3",
|
|
Ready: false,
|
|
State: corev1.ContainerState{Waiting: &corev1.ContainerStateWaiting{}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "multi-init-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Init:1/3", rows[0].Cells[2])
|
|
},
|
|
},
|
|
{
|
|
name: "Pod with container terminated with signal",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "terminated-signal-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-1 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "container1",
|
|
State: corev1.ContainerState{
|
|
Terminated: &corev1.ContainerStateTerminated{
|
|
Signal: 15,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "terminated-signal-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Signal:15", rows[0].Cells[2])
|
|
},
|
|
},
|
|
{
|
|
name: "Pod with container terminated with exit code",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "terminated-exit-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-2 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "container1",
|
|
State: corev1.ContainerState{
|
|
Terminated: &corev1.ContainerStateTerminated{
|
|
ExitCode: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "terminated-exit-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "ExitCode:2", rows[0].Cells[2])
|
|
},
|
|
},
|
|
{
|
|
name: "Running pod with ready condition",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "ready-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-3 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
Conditions: []corev1.PodCondition{
|
|
{Type: corev1.PodReady, Status: corev1.ConditionTrue},
|
|
},
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "container1",
|
|
Ready: true,
|
|
State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "ready-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "1/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Running", rows[0].Cells[2])
|
|
},
|
|
},
|
|
{
|
|
name: "Running pod without ready condition",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "not-ready-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-4 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "container1",
|
|
Ready: false,
|
|
State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "not-ready-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Running", rows[0].Cells[2])
|
|
},
|
|
},
|
|
{
|
|
name: "Running pod with container not ready",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "running-not-ready-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-55 * time.Minute)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "main-container"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "main-container",
|
|
Ready: false,
|
|
State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "running-not-ready-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "0/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Running", rows[0].Cells[2])
|
|
},
|
|
},
|
|
{
|
|
name: "Completed pod with running container",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "completed-running-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-50 * time.Minute)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "main-container"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
Conditions: []corev1.PodCondition{
|
|
{Type: corev1.PodReady, Status: corev1.ConditionTrue},
|
|
},
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "main-container",
|
|
Ready: true,
|
|
State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "completed-running-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "1/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Running", rows[0].Cells[2])
|
|
},
|
|
},
|
|
{
|
|
name: "Pod with multiple IPs",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "multi-ip-pod",
|
|
CreationTimestamp: metav1.Time{Time: time.Now().Add(-5 * time.Hour)},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{{Name: "container1"}},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
PodIPs: []corev1.PodIP{
|
|
{IP: "192.168.1.10"},
|
|
{IP: "fd00::10"},
|
|
},
|
|
ContainerStatuses: []corev1.ContainerStatus{
|
|
{
|
|
Name: "container1",
|
|
Ready: true,
|
|
State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
options: printers.GenerateOptions{Wide: true},
|
|
expectedChecks: func(t *testing.T, rows []metav1.TableRow) {
|
|
assert.Len(t, rows, 1)
|
|
assert.Equal(t, "multi-ip-pod", rows[0].Cells[0])
|
|
assert.Equal(t, "1/1", rows[0].Cells[1])
|
|
assert.Equal(t, "Running", rows[0].Cells[2])
|
|
assert.Equal(t, "192.168.1.10", rows[0].Cells[5]) // IP column in wide output
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
rows, err := printPod(tc.pod, tc.options)
|
|
assert.NoError(t, err)
|
|
tc.expectedChecks(t, rows)
|
|
assert.Equal(t, runtime.RawExtension{Object: tc.pod}, rows[0].Object)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHasPodReadyCondition(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
conditions []corev1.PodCondition
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Empty conditions",
|
|
conditions: []corev1.PodCondition{},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Ready condition is true",
|
|
conditions: []corev1.PodCondition{
|
|
{
|
|
Type: corev1.PodReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Ready condition is false",
|
|
conditions: []corev1.PodCondition{
|
|
{
|
|
Type: corev1.PodReady,
|
|
Status: corev1.ConditionFalse,
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Ready condition is unknown",
|
|
conditions: []corev1.PodCondition{
|
|
{
|
|
Type: corev1.PodReady,
|
|
Status: corev1.ConditionUnknown,
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Multiple conditions, Ready is true",
|
|
conditions: []corev1.PodCondition{
|
|
{
|
|
Type: corev1.PodInitialized,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
{
|
|
Type: corev1.PodReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
{
|
|
Type: corev1.ContainersReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Multiple conditions, Ready is false",
|
|
conditions: []corev1.PodCondition{
|
|
{
|
|
Type: corev1.PodInitialized,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
{
|
|
Type: corev1.PodReady,
|
|
Status: corev1.ConditionFalse,
|
|
},
|
|
{
|
|
Type: corev1.ContainersReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result := hasPodReadyCondition(tc.conditions)
|
|
assert.Equal(t, tc.expected, result, "hasPodReadyCondition returned unexpected result")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrintCoreV1(t *testing.T) {
|
|
testCases := []struct {
|
|
pod corev1.Pod
|
|
generateOptions printers.GenerateOptions
|
|
expect []metav1.TableRow
|
|
}{
|
|
// Test name, kubernetes version, sync mode, cluster ready status,
|
|
{
|
|
corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test1"},
|
|
Spec: corev1.PodSpec{},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodPending,
|
|
HostIP: "1.2.3.4",
|
|
PodIP: "2.3.4.5",
|
|
},
|
|
},
|
|
printers.GenerateOptions{Wide: false},
|
|
[]metav1.TableRow{{Cells: []interface{}{"test1", "0/0", "Pending", int64(0), "<unknown>"}}},
|
|
},
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
tc := tc
|
|
t.Run(fmt.Sprintf("pod=%s, expected value=%v", tc.pod.Name, tc.expect), func(t *testing.T) {
|
|
rows, err := printPod(&tc.pod, tc.generateOptions)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i := range rows {
|
|
rows[i].Object.Object = nil
|
|
}
|
|
if !reflect.DeepEqual(tc.expect, rows) {
|
|
t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(tc.expect, rows))
|
|
}
|
|
})
|
|
}
|
|
}
|