Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sort containers in index order #2878

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 8 additions & 30 deletions internal/dao/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)

var (
Expand Down Expand Up @@ -47,11 +46,15 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error
return nil, err
}
res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers))
for _, co := range po.Spec.InitContainers {
res = append(res, makeContainerRes(co, po, cmx[co.Name], true))
for i, co := range po.Spec.InitContainers {
ryanbrainard marked this conversation as resolved.
Show resolved Hide resolved
cr := render.MakeContainerRes(po, true, i)
cr.MX = cmx[co.Name]
res = append(res, cr)
}
for _, co := range po.Spec.Containers {
res = append(res, makeContainerRes(co, po, cmx[co.Name], false))
for i, co := range po.Spec.Containers {
cr := render.MakeContainerRes(po, false, i)
cr.MX = cmx[co.Name]
res = append(res, cr)
}

return res, nil
Expand All @@ -68,31 +71,6 @@ func (c *Container) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan,
// ----------------------------------------------------------------------------
// Helpers...

func makeContainerRes(co v1.Container, po *v1.Pod, cmx *mv1beta1.ContainerMetrics, isInit bool) render.ContainerRes {
return render.ContainerRes{
Container: &co,
Status: getContainerStatus(co.Name, po.Status),
MX: cmx,
IsInit: isInit,
Age: po.GetCreationTimestamp(),
}
}

func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus {
for _, c := range status.ContainerStatuses {
if c.Name == co {
return &c
}
}
for _, c := range status.InitContainerStatuses {
if c.Name == co {
return &c
}
}

return nil
}

func (c *Container) fetchPod(fqn string) (*v1.Pod, error) {
o, err := c.getFactory().Get("v1/pods", fqn, true, labels.Everything())
if err != nil {
Expand Down
80 changes: 65 additions & 15 deletions internal/render/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ func (c Container) ColorerFunc() model1.ColorerFunc {
// Header returns a header row.
func (Container) Header(ns string) model1.Header {
return model1.Header{
model1.HeaderColumn{Name: "IDX", Align: tview.AlignRight},
model1.HeaderColumn{Name: "NAME"},
model1.HeaderColumn{Name: "PF"},
model1.HeaderColumn{Name: "IMAGE"},
model1.HeaderColumn{Name: "READY"},
model1.HeaderColumn{Name: "STATE"},
model1.HeaderColumn{Name: "INIT"},
model1.HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight},
model1.HeaderColumn{Name: "PROBES(L:R)"},
model1.HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true},
Expand All @@ -101,22 +101,22 @@ func (c Container) Render(o interface{}, name string, r *model1.Row) error {
return fmt.Errorf("expected ContainerRes, but got %T", o)
}

cur, res := gatherMetrics(co.Container, co.MX)
cur, res := gatherMetrics(co.Container(), co.MX)
ryanbrainard marked this conversation as resolved.
Show resolved Hide resolved
ready, state, restarts := "false", MissingValue, "0"
if co.Status != nil {
ready, state, restarts = boolToStr(co.Status.Ready), ToContainerState(co.Status.State), strconv.Itoa(int(co.Status.RestartCount))
if co.Status() != nil {
ready, state, restarts = boolToStr(co.Status().Ready), ToContainerState(co.Status().State), strconv.Itoa(int(co.Status().RestartCount))
}

r.ID = co.Container.Name
r.ID = co.IndexLabel()
ryanbrainard marked this conversation as resolved.
Show resolved Hide resolved
r.Fields = model1.Fields{
co.Container.Name,
co.IndexLabel(),
co.Container().Name,
ryanbrainard marked this conversation as resolved.
Show resolved Hide resolved
"●",
co.Container.Image,
co.Container().Image,
ready,
state,
boolToStr(co.IsInit),
restarts,
probe(co.Container.LivenessProbe) + ":" + probe(co.Container.ReadinessProbe),
probe(co.Container().LivenessProbe) + ":" + probe(co.Container().ReadinessProbe),
toMc(cur.cpu),
toMi(cur.mem),
toMc(res.cpu) + ":" + toMc(res.lcpu),
Expand All @@ -125,7 +125,7 @@ func (c Container) Render(o interface{}, name string, r *model1.Row) error {
client.ToPercentageStr(cur.cpu, res.lcpu),
client.ToPercentageStr(cur.mem, res.mem),
client.ToPercentageStr(cur.mem, res.lmem),
ToContainerPorts(co.Container.Ports),
ToContainerPorts(co.Container().Ports),
AsStatus(c.diagnose(state, ready)),
ToAge(co.Age),
}
Expand Down Expand Up @@ -236,13 +236,23 @@ func probe(p *v1.Probe) string {
return on
}

func MakeContainerRes(po *v1.Pod, isInit bool, index int) ContainerRes {
return ContainerRes{
pod: po,
IsInit: isInit,
Index: index,
Age: po.GetCreationTimestamp(),
}
}

// ContainerRes represents a container and its metrics.
type ContainerRes struct {
ryanbrainard marked this conversation as resolved.
Show resolved Hide resolved
Container *v1.Container
Status *v1.ContainerStatus
MX *mv1beta1.ContainerMetrics
IsInit bool
Age metav1.Time
pod *v1.Pod
IsInit bool
// Index is the container's index in either the Containers or InitContainers.
Index int
MX *mv1beta1.ContainerMetrics
Age metav1.Time
}

// GetObjectKind returns a schema object.
Expand All @@ -254,3 +264,43 @@ func (c ContainerRes) GetObjectKind() schema.ObjectKind {
func (c ContainerRes) DeepCopyObject() runtime.Object {
return c
}

// Container returns the underlying container or init container
func (c ContainerRes) Container() *v1.Container {
if c.IsInit {
if c.Index >= 0 && c.Index < len(c.pod.Spec.InitContainers) {
return &c.pod.Spec.InitContainers[c.Index]
}
return nil
}
if c.Index >= 0 && c.Index < len(c.pod.Spec.Containers) {
return &c.pod.Spec.Containers[c.Index]
}
return nil
}

// Status returns the underlying container or init container status
func (c ContainerRes) Status() *v1.ContainerStatus {
if c.IsInit {
if c.Index >= 0 && c.Index < len(c.pod.Status.InitContainerStatuses) {
return &c.pod.Status.InitContainerStatuses[c.Index]
}
return nil
}
if c.Index >= 0 && c.Index < len(c.pod.Status.ContainerStatuses) {
return &c.pod.Status.ContainerStatuses[c.Index]
}
return nil
}

// IndexLabel returns a label for the index with a prefix for the container type.
// Sorts so init containers are first.
func (c ContainerRes) IndexLabel() string {
var containerType string
if c.IsInit {
containerType = "\u24D8" // encircled i (sorts first)
ryanbrainard marked this conversation as resolved.
Show resolved Hide resolved
} else {
containerType = "\u2800" // blank (sorts last)
}
return fmt.Sprintf("%s %d", containerType, c.Index)
ryanbrainard marked this conversation as resolved.
Show resolved Hide resolved
}
124 changes: 109 additions & 15 deletions internal/render/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package render_test

import (
"fmt"
"github.com/fvbommel/sortorder"
"sort"
"testing"
"time"

Expand All @@ -17,26 +19,94 @@ import (
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)

func TestContainerRes_IndexLabel(t *testing.T) {
cresInit0 := render.ContainerRes{
Index: 0,
IsInit: true,
}
cresInit1 := render.ContainerRes{
Index: 1,
IsInit: true,
}
cresReg0 := render.ContainerRes{
Index: 0,
IsInit: false,
}
cresReg1 := render.ContainerRes{
Index: 1,
IsInit: false,
}

assert.Equal(t, cresInit0.IndexLabel(), "ⓘ 0")
assert.Equal(t, cresInit1.IndexLabel(), "ⓘ 1")
assert.Equal(t, cresReg0.IndexLabel(), "⠀ 0")
assert.Equal(t, cresReg1.IndexLabel(), "⠀ 1")

assert.True(t, sort.IsSorted(sortorder.Natural{
cresInit0.IndexLabel(),
cresInit1.IndexLabel(),
cresReg0.IndexLabel(),
cresReg1.IndexLabel(),
}))
}

func TestContainer(t *testing.T) {
var c render.Container

cres := render.ContainerRes{
Container: makeContainer(),
Status: makeContainerStatus(),
MX: makeContainerMetrics(),
IsInit: false,
Age: makeAge(),
}
cres := makeContainerRes(
makeContainer(),
makeContainerStatus(),
makeContainerMetrics(),
false,
makeAge(),
)
var r model1.Row
assert.Nil(t, c.Render(cres, "blee", &r))
assert.Equal(t, "fred", r.ID)
assert.Equal(t, "⠀ 0", r.ID)
assert.Equal(t, model1.Fields{
"⠀ 0",
"fred",
"●",
"img",
"false",
"Running",
"0",
"off:off",
"10",
"20",
"20:20",
"100:100",
"50",
"50",
"20",
"20",
"",
"container is not ready",
},
r.Fields[:len(r.Fields)-1],
)
}

func TestInitContainer(t *testing.T) {
var c render.Container

cres := makeContainerRes(
makeContainer(),
makeContainerStatus(),
makeContainerMetrics(),
true,
makeAge(),
)
var r model1.Row
assert.Nil(t, c.Render(cres, "blee", &r))
assert.Equal(t, "ⓘ 0", r.ID)
assert.Equal(t, model1.Fields{
"ⓘ 0",
"fred",
"●",
"img",
"false",
"Running",
"0",
"off:off",
"10",
Expand All @@ -57,13 +127,13 @@ func TestContainer(t *testing.T) {
func BenchmarkContainerRender(b *testing.B) {
var c render.Container

cres := render.ContainerRes{
Container: makeContainer(),
Status: makeContainerStatus(),
MX: makeContainerMetrics(),
IsInit: false,
Age: makeAge(),
}
cres := makeContainerRes(
makeContainer(),
makeContainerStatus(),
makeContainerMetrics(),
false,
makeAge(),
)
var r model1.Row

b.ReportAllocs()
Expand All @@ -76,6 +146,30 @@ func BenchmarkContainerRender(b *testing.B) {
// ----------------------------------------------------------------------------
// Helpers...

func makeContainerRes(container *v1.Container, status *v1.ContainerStatus, cmx *mv1beta1.ContainerMetrics, isInit bool, age metav1.Time) render.ContainerRes {
po := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
CreationTimestamp: age,
},
}

if isInit {
po.Spec.InitContainers = []v1.Container{*container}
po.Status.InitContainerStatuses = []v1.ContainerStatus{*status}
} else {
po.Spec.Containers = []v1.Container{*container}
po.Status.ContainerStatuses = []v1.ContainerStatus{*status}
}

cr := render.MakeContainerRes(
po,
isInit,
0,
)
cr.MX = cmx
return cr
}

func toQty(s string) resource.Quantity {
q, _ := resource.ParseQuantity(s)
return q
Expand Down
2 changes: 2 additions & 0 deletions internal/view/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func NewContainer(gvr client.GVR) ResourceViewer {
c.GetTable().SetDecorateFn(c.decorateRows)
c.AddBindKeysFn(c.bindKeys)
c.GetTable().SetDecorateFn(c.portForwardIndicator)
c.GetTable().SetSortCol("IDX", true)

return &c
}
Expand Down Expand Up @@ -90,6 +91,7 @@ func (c *Container) bindKeys(aa *ui.KeyActions) {
ui.KeyF: ui.NewKeyAction("Show PortForward", c.showPFCmd, true),
ui.KeyShiftF: ui.NewKeyAction("PortForward", c.portFwdCmd, true),
ui.KeyShiftT: ui.NewKeyAction("Sort Restart", c.GetTable().SortColCmd("RESTARTS", false), false),
ui.KeyShiftI: ui.NewKeyAction("Sort Index", c.GetTable().SortColCmd("IDX", true), false),
})
aa.Merge(resourceSorters(c.GetTable()))
}
Expand Down
2 changes: 1 addition & 1 deletion internal/view/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ func TestContainerNew(t *testing.T) {

assert.Nil(t, c.Init(makeCtx()))
assert.Equal(t, "Containers", c.Name())
assert.Equal(t, 18, len(c.Hints()))
assert.Equal(t, 19, len(c.Hints()))
}
4 changes: 2 additions & 2 deletions internal/xray/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ func (c *Container) Render(ctx context.Context, ns string, o interface{}) error
return fmt.Errorf("no factory found in context")
}

root := NewTreeNode("containers", client.FQN(ns, co.Container.Name))
root := NewTreeNode("containers", client.FQN(ns, co.Container().Name))
parent, ok := ctx.Value(KeyParent).(*TreeNode)
if !ok {
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
}
pns, _ := client.Namespaced(parent.ID)
c.envRefs(f, root, pns, co.Container)
c.envRefs(f, root, pns, co.Container())
parent.Add(root)

return nil
Expand Down
Loading