Feature: add pod last restart column to pod section (#2946)
* feat: create ToRestartAge helper function to calculate last restart age * feat: add Last Restart column to pod view * test: add TestPodLastRestart to pod tests * test: add TestToRestartAge tests * test: fix all initial row states on table tests * chore: remove TestToRestartAge changes * refactor: move LastRestart pod function to internal function * refactor: update to new go 1.22 range over map syntaxmine
parent
9984e3f4bf
commit
92ff64eac3
|
|
@ -35,7 +35,7 @@ func TestTableReconcile(t *testing.T) {
|
|||
err := ta.reconcile(ctx)
|
||||
assert.Nil(t, err)
|
||||
data := ta.Peek()
|
||||
assert.Equal(t, 23, data.HeaderCount())
|
||||
assert.Equal(t, 24, data.HeaderCount())
|
||||
assert.Equal(t, 1, data.RowCount())
|
||||
assert.Equal(t, client.NamespaceAll, data.GetNamespace())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ func TestTableRefresh(t *testing.T) {
|
|||
ctx = context.WithValue(ctx, internal.KeyWithMetrics, false)
|
||||
assert.NoError(t, ta.Refresh(ctx))
|
||||
data := ta.Peek()
|
||||
assert.Equal(t, 23, data.HeaderCount())
|
||||
assert.Equal(t, 24, data.HeaderCount())
|
||||
assert.Equal(t, 1, data.RowCount())
|
||||
assert.Equal(t, client.NamespaceAll, data.GetNamespace())
|
||||
assert.Equal(t, 1, l.count)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ func TestTableHydrate(t *testing.T) {
|
|||
|
||||
assert.Nil(t, model1.Hydrate("blee", oo, rr, Pod{}))
|
||||
assert.Equal(t, 1, len(rr))
|
||||
assert.Equal(t, 23, len(rr[0].Fields))
|
||||
assert.Equal(t, 24, len(rr[0].Fields))
|
||||
}
|
||||
|
||||
func TestToAge(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/derailed/tview"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
|
@ -92,6 +93,7 @@ func (p Pod) Header(ns string) model1.Header {
|
|||
model1.HeaderColumn{Name: "READY"},
|
||||
model1.HeaderColumn{Name: "STATUS"},
|
||||
model1.HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight},
|
||||
model1.HeaderColumn{Name: "LAST RESTART", Align: tview.AlignRight, Time: true, Wide: true},
|
||||
model1.HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true},
|
||||
model1.HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true},
|
||||
model1.HeaderColumn{Name: "CPU/R:L", Align: tview.AlignRight, Wide: true},
|
||||
|
|
@ -127,6 +129,7 @@ func (p Pod) Render(o interface{}, ns string, row *model1.Row) error {
|
|||
_, _, irc := p.Statuses(ics)
|
||||
cs := po.Status.ContainerStatuses
|
||||
cr, _, rc := p.Statuses(cs)
|
||||
lr := p.lastRestart(cs)
|
||||
|
||||
var ccmx []mv1beta1.ContainerMetrics
|
||||
if pwm.MX != nil {
|
||||
|
|
@ -144,6 +147,7 @@ func (p Pod) Render(o interface{}, ns string, row *model1.Row) error {
|
|||
strconv.Itoa(cr) + "/" + strconv.Itoa(len(po.Spec.Containers)),
|
||||
phase,
|
||||
strconv.Itoa(rc + irc),
|
||||
ToAge(lr),
|
||||
toMc(c.cpu),
|
||||
toMi(c.mem),
|
||||
toMc(r.cpu) + ":" + toMc(r.lcpu),
|
||||
|
|
@ -317,6 +321,20 @@ func (*Pod) Statuses(ss []v1.ContainerStatus) (cr, ct, rc int) {
|
|||
return
|
||||
}
|
||||
|
||||
// lastRestart returns the last container restart time.
|
||||
func (*Pod) lastRestart(ss []v1.ContainerStatus) (latest metav1.Time) {
|
||||
for _, c := range ss {
|
||||
if c.LastTerminationState.Terminated == nil {
|
||||
continue
|
||||
}
|
||||
ts := c.LastTerminationState.Terminated.FinishedAt
|
||||
if latest.IsZero() || ts.After(latest.Time) {
|
||||
latest = ts
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Phase reports the given pod phase.
|
||||
func (p *Pod) Phase(po *v1.Pod) string {
|
||||
status := string(po.Status.Phase)
|
||||
|
|
|
|||
|
|
@ -4,12 +4,15 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
res "k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||
)
|
||||
|
||||
|
|
@ -354,6 +357,81 @@ func Test_filterSidecarCO(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_lastRestart(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
containerStatuses []v1.ContainerStatus
|
||||
expected metav1.Time
|
||||
}{
|
||||
"no-restarts": {
|
||||
containerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: "c1",
|
||||
LastTerminationState: v1.ContainerState{},
|
||||
},
|
||||
},
|
||||
expected: metav1.Time{},
|
||||
},
|
||||
"single-container-restart": {
|
||||
containerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: "c1",
|
||||
LastTerminationState: v1.ContainerState{
|
||||
Terminated: &v1.ContainerStateTerminated{
|
||||
FinishedAt: metav1.Time{Time: testTime()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: metav1.Time{Time: testTime()},
|
||||
},
|
||||
"multiple-container-restarts": {
|
||||
containerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: "c1",
|
||||
LastTerminationState: v1.ContainerState{
|
||||
Terminated: &v1.ContainerStateTerminated{
|
||||
FinishedAt: metav1.Time{Time: testTime().Add(-1 * time.Hour)},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "c2",
|
||||
LastTerminationState: v1.ContainerState{
|
||||
Terminated: &v1.ContainerStateTerminated{
|
||||
FinishedAt: metav1.Time{Time: testTime()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: metav1.Time{Time: testTime()},
|
||||
},
|
||||
"mixed-termination-states": {
|
||||
containerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: "c1",
|
||||
LastTerminationState: v1.ContainerState{},
|
||||
},
|
||||
{
|
||||
Name: "c2",
|
||||
LastTerminationState: v1.ContainerState{
|
||||
Terminated: &v1.ContainerStateTerminated{
|
||||
FinishedAt: metav1.Time{Time: testTime()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: metav1.Time{Time: testTime()},
|
||||
},
|
||||
}
|
||||
|
||||
var p Pod
|
||||
for name, u := range uu {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.Equal(t, u.expected, p.lastRestart(u.containerStatuses))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_gatherPodMx(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
spec *v1.PodSpec
|
||||
|
|
@ -546,3 +624,11 @@ func makeCoMX(n string, c, m string) mv1beta1.ContainerMetrics {
|
|||
Usage: makeRes(c, m),
|
||||
}
|
||||
}
|
||||
|
||||
func testTime() time.Time {
|
||||
t, err := time.Parse(time.RFC3339, "2018-12-14T10:36:43.326972-07:00")
|
||||
if err != nil {
|
||||
fmt.Println("TestTime Failed", err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,8 +163,8 @@ func TestPodRender(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "default/nginx", r.ID)
|
||||
e := model1.Fields{"default", "nginx", "0", "●", "1/1", "Running", "0", "100", "50", "100:0", "70:170", "100", "n/a", "71", "29", "172.17.0.6", "minikube", "<none>", "<none>"}
|
||||
assert.Equal(t, e, r.Fields[:19])
|
||||
e := model1.Fields{"default", "nginx", "0", "●", "1/1", "Running", "0", "<unknown>", "100", "50", "100:0", "70:170", "100", "n/a", "71", "29", "172.17.0.6", "minikube", "<none>", "<none>"}
|
||||
assert.Equal(t, e, r.Fields[:20])
|
||||
}
|
||||
|
||||
func BenchmarkPodRender(b *testing.B) {
|
||||
|
|
@ -194,8 +194,8 @@ func TestPodInitRender(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "default/nginx", r.ID)
|
||||
e := model1.Fields{"default", "nginx", "0", "●", "1/1", "Init:0/1", "0", "10", "10", "100:0", "70:170", "10", "n/a", "14", "5", "172.17.0.6", "minikube", "<none>", "<none>"}
|
||||
assert.Equal(t, e, r.Fields[:19])
|
||||
e := model1.Fields{"default", "nginx", "0", "●", "1/1", "Init:0/1", "0", "<unknown>", "10", "10", "100:0", "70:170", "10", "n/a", "14", "5", "172.17.0.6", "minikube", "<none>", "<none>"}
|
||||
assert.Equal(t, e, r.Fields[:20])
|
||||
}
|
||||
|
||||
func TestPodSidecarRender(t *testing.T) {
|
||||
|
|
@ -210,8 +210,8 @@ func TestPodSidecarRender(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "default/sleep", r.ID)
|
||||
e := model1.Fields{"default", "sleep", "0", "●", "1/1", "Running", "0", "100", "40", "50:250", "50:80", "200", "40", "80", "50", "10.244.0.8", "kind-control-plane", "<none>", "<none>"}
|
||||
assert.Equal(t, e, r.Fields[:19])
|
||||
e := model1.Fields{"default", "sleep", "0", "●", "1/1", "Running", "0", "<unknown>", "100", "40", "50:250", "50:80", "200", "40", "80", "50", "10.244.0.8", "kind-control-plane", "<none>", "<none>"}
|
||||
assert.Equal(t, e, r.Fields[:20])
|
||||
}
|
||||
|
||||
func TestCheckPodStatus(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue