k9s/internal/watch/container.go

341 lines
6.5 KiB
Go

package watch
import (
"fmt"
"strconv"
"github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/watch"
)
const (
// ContainerIndex marker for stored containers.
ContainerIndex string = "co"
containerCols = 12
)
// Container tracks container activities.
type Container struct {
CallbackInformer
data RowEvents
mxData k8s.PodMetrics
listener TableListenerFn
activeFQN *string
}
// NewContainer returns a new container.
func NewContainer(po CallbackInformer) *Container {
co := Container{
CallbackInformer: po,
data: RowEvents{},
}
po.AddEventHandler(&co)
return &co
}
// Run starts out the informer loop.
func (c *Container) Run(closeCh <-chan struct{}) {}
// Get retrieves a given container from store.
func (c *Container) Get(fqn string) (interface{}, error) {
o, ok, err := c.GetStore().GetByKey(fqn)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("Container %s not found", fqn)
}
return o, nil
}
// List retrieves a given containers from store.
func (c *Container) List(fqn string) (k8s.Collection, error) {
o, ok, err := c.GetStore().GetByKey(fqn)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("Pod<containers> %s not found", fqn)
}
po := o.(*v1.Pod)
var cc k8s.Collection
for i := 0; i < len(po.Spec.InitContainers); i++ {
cc = append(cc, &po.Spec.InitContainers[i])
}
for i := 0; i < len(po.Spec.Containers); i++ {
cc = append(cc, &po.Spec.Containers[i])
}
return cc, nil
}
// SetListener registers event recipient.
func (c *Container) SetListener(fqn string, cb TableListenerFn) {
c.listener, c.activeFQN = cb, &fqn
o, err := c.Get(fqn)
if err != nil {
log.Error().Err(err).Msgf("Pod `%q not found", fqn)
return
}
// Clear out all rows
for k := range c.data {
delete(c.data, k)
}
c.updateData(watch.Added, o.(*v1.Pod))
c.fireChanged()
}
// UnsetListener unregister event recipient.
func (c *Container) UnsetListener(_ string) {
c.listener, c.activeFQN = nil, nil
}
// Data return current data.
func (c *Container) tableData(ns string) TableData {
return TableData{
Header: c.header(),
Rows: c.data,
Namespace: ns,
}
}
func (c *Container) fireChanged() {
if cb := c.listener; cb != nil {
cb(c.tableData(NotNamespaced))
}
}
// StartWatching registers container event listener.
func (c *Container) StartWatching(stopCh <-chan struct{}) {}
// OnAdd notify container added.
func (c *Container) OnAdd(obj interface{}) {
if c.activeFQN == nil {
return
}
po := obj.(*v1.Pod)
fqn := MetaFQN(po.ObjectMeta)
if fqn != *c.activeFQN {
return
}
log.Debug().Msgf("Pod Added %s", fqn)
// ff := make(Row, containerCols)
c.updateData(watch.Added, po)
if c.HasSynced() {
c.fireChanged()
}
}
// OnUpdate notify container updated.
func (c *Container) OnUpdate(oldObj, newObj interface{}) {
if c.activeFQN == nil {
return
}
opo, npo := oldObj.(*v1.Pod), newObj.(*v1.Pod)
k1 := MetaFQN(opo.ObjectMeta)
k2 := MetaFQN(npo.ObjectMeta)
if k1 != *c.activeFQN && k2 != *c.activeFQN {
return
}
log.Debug().Msgf("Pod Updated %#v - %#v", opo.Name, npo.Name)
// Check if this is a rollout
if k1 != k2 {
c.updateData(watch.Modified, opo)
} else {
c.updateData(watch.Modified, npo)
}
c.fireChanged()
}
// OnDelete notify container was deleted.
func (c *Container) OnDelete(obj interface{}) {
if c.activeFQN == nil {
return
}
po := obj.(*v1.Pod)
fqn := MetaFQN(po.ObjectMeta)
if fqn != *c.activeFQN {
return
}
log.Debug().Msgf("Pod Deleted %s", fqn)
c.data = RowEvents{}
c.fireChanged()
}
// header return resource header.
func (*Container) header() Row {
var hh Row
return append(hh,
"NAME",
"IMAGE",
"READY",
"STATE",
"RS",
"LPROB",
"RPROB",
"CPU",
"MEM",
"RCPU",
"RMEM",
"AGE",
)
}
// Fields retrieves displayable fields.
func (c *Container) fields(pod *v1.Pod, co v1.Container) Row {
ff := make(Row, 0, containerCols)
// mxs, _ := c.MetricsServer.FetchPodsMetrics(r.pod.Namespace)
// var cpu, mem string
// for _, mx := range mxs.Items {
// if mx.Name != r.pod.Name {
// continue
// }
// for _, co := range mx.Containers {
// if co.Name != i.Name {
// continue
// }
// cpu, mem = toRes(co.Usage)
// }
// }
// rcpu, rmem := resources(i)
var cs *v1.ContainerStatus
for _, cos := range pod.Status.ContainerStatuses {
if cos.Name != co.Name {
continue
}
cs = &cos
}
if cs == nil {
for _, cos := range pod.Status.InitContainerStatuses {
if cos.Name != co.Name {
continue
}
cs = &cos
}
}
ready, state, restarts := "false", MissingValue, "0"
if cs != nil {
ready, state, restarts = boolToStr(cs.Ready), toState(cs.State), strconv.Itoa(int(cs.RestartCount))
}
cpu, mem, rcpu, rmem := "Z", "Z", "Z", "Z"
return append(ff,
co.Name,
co.Image,
ready,
state,
restarts,
probe(co.LivenessProbe),
probe(co.ReadinessProbe),
cpu,
mem,
rcpu,
rmem,
toAge(pod.CreationTimestamp),
)
}
func (c *Container) updateData(action watch.EventType, po *v1.Pod) {
for _, co := range po.Spec.InitContainers {
ff := c.fields(po, co)
if re, ok := c.data[co.Name]; ok {
re.Action = action
re.Deltas = re.Fields
re.Fields = ff
} else {
c.data[co.Name] = &RowEvent{
Action: action,
Deltas: make(Row, containerCols),
Fields: ff,
}
}
}
for _, co := range po.Spec.Containers {
ff := c.fields(po, co)
if re, ok := c.data[co.Name]; ok {
re.Action = action
re.Deltas = re.Fields
re.Fields = ff
} else {
c.data[co.Name] = &RowEvent{
Action: action,
Deltas: make(Row, containerCols),
Fields: ff,
}
}
}
}
// ----------------------------------------------------------------------------
// Helpers...
func toState(s v1.ContainerState) string {
switch {
case s.Waiting != nil:
if s.Waiting.Reason != "" {
return s.Waiting.Reason
}
return "Waiting"
case s.Terminated != nil:
if s.Terminated.Reason != "" {
return s.Terminated.Reason
}
return "Terminated"
case s.Running != nil:
return "Running"
default:
return MissingValue
}
}
func toRes(r v1.ResourceList) (string, string) {
cpu, mem := r[v1.ResourceCPU], r[v1.ResourceMemory]
return ToMillicore(cpu.MilliValue()), ToMi(k8s.ToMB(mem.Value()))
}
func resources(c v1.Container) (cpu, mem string) {
req, lim := c.Resources.Requests, c.Resources.Limits
if len(req) == 0 {
if len(lim) != 0 {
return toRes(lim)
}
} else {
return toRes(req)
}
return "0", "0"
}
func probe(p *v1.Probe) string {
if p == nil {
return "no"
}
return "yes"
}