k9s/internal/resource/container.go

269 lines
5.2 KiB
Go

package resource
import (
"bufio"
"context"
"strconv"
"sync"
"time"
"github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
type (
// Container represents a container on a pod.
Container struct {
*Base
pod *v1.Pod
isInit bool
instance v1.Container
MetricsServer MetricsServer
metrics *mv1beta1.PodMetrics
mx sync.RWMutex
}
)
// NewContainerList returns a collection of container.
func NewContainerList(c Connection, mx MetricsServer, pod *v1.Pod) List {
return NewList(
"",
"co",
NewContainer(c, mx, pod),
0,
)
}
// NewContainer returns a new set of containers.
func NewContainer(c Connection, mx MetricsServer, pod *v1.Pod) *Container {
co := Container{
Base: &Base{Connection: c, Resource: k8s.NewPod(c)},
pod: pod,
MetricsServer: mx,
}
co.Factory = &co
return &co
}
// New builds a new Container instance from a k8s resource.
func (r *Container) New(i interface{}) Columnar {
co := NewContainer(r.Connection, r.MetricsServer, r.pod)
co.instance = i.(v1.Container)
co.path = r.namespacedName(r.pod.ObjectMeta) + ":" + co.instance.Name
return co
}
// SetPodMetrics set the current k8s resource metrics on associated pod.
func (r *Container) SetPodMetrics(m *mv1beta1.PodMetrics) {
r.metrics = m
}
// Marshal resource to yaml.
func (r *Container) Marshal(path string) (string, error) {
return "", nil
}
// Logs tails a given container logs
func (r *Container) Logs(c chan<- string, ns, n, co string, lines int64, prev bool) (context.CancelFunc, error) {
req := r.Resource.(k8s.Loggable).Logs(ns, n, co, lines, prev)
ctx, cancel := context.WithCancel(context.TODO())
req.Context(ctx)
blocked := true
go func() {
select {
case <-time.After(defaultTimeout):
var closes bool
r.mx.RLock()
{
closes = blocked
}
r.mx.RUnlock()
if closes {
log.Debug().Msg(">>Closing Channel<<")
close(c)
cancel()
}
}
}()
// This call will block if nothing is in the stream!!
stream, err := req.Stream()
if err != nil {
log.Warn().Err(err).Msgf("Stream canceled `%s/%s:%s", ns, n, co)
return cancel, err
}
r.mx.Lock()
{
blocked = false
}
r.mx.Unlock()
go func() {
defer func() {
log.Debug().Msgf("Closing stream %s:%s", n, co)
close(c)
stream.Close()
cancel()
}()
scanner := bufio.NewScanner(stream)
for scanner.Scan() {
c <- scanner.Text()
select {
case <-ctx.Done():
return
default:
}
}
}()
return cancel, nil
}
// List resources for a given namespace.
func (r *Container) List(ns string) (Columnars, error) {
icos := r.pod.Spec.InitContainers
cos := r.pod.Spec.Containers
cc := make(Columnars, 0, len(icos)+len(cos))
for _, co := range icos {
ci := r.New(co)
ci.(*Container).isInit = true
cc = append(cc, ci)
}
for _, co := range cos {
cc = append(cc, r.New(co))
}
return cc, nil
}
// Header return resource header.
func (*Container) Header(ns string) Row {
return append(Row{},
"NAME",
"IMAGE",
"READY",
"STATE",
"RS",
"PROBES(L:R)",
"CPU",
"MEM",
"%CPU",
"%MEM",
"AGE",
)
}
// Fields retrieves displayable fields.
func (r *Container) Fields(ns string) Row {
ff := make(Row, 0, len(r.Header(ns)))
i := r.instance
scpu, smem, pcpu, pmem := NAValue, NAValue, NAValue, NAValue
if r.metrics != nil {
var (
cpu int64
mem float64
)
for _, co := range r.metrics.Containers {
if co.Name == i.Name {
cpu = co.Usage.Cpu().MilliValue()
mem = k8s.ToMB(co.Usage.Memory().Value())
break
}
}
scpu, smem = ToMillicore(cpu), ToMi(mem)
rcpu, rmem := containerResources(i)
if rcpu != nil {
pcpu = AsPerc(toPerc(float64(cpu), float64(rcpu.MilliValue())))
}
if rmem != nil {
pmem = AsPerc(toPerc(mem, k8s.ToMB(rmem.Value())))
}
}
var cs *v1.ContainerStatus
for _, c := range r.pod.Status.ContainerStatuses {
if c.Name != i.Name {
continue
}
cs = &c
}
if cs == nil {
for _, c := range r.pod.Status.InitContainerStatuses {
if c.Name != i.Name {
continue
}
cs = &c
}
}
ready, state, restarts := "false", MissingValue, "0"
if cs != nil {
ready, state, restarts = boolToStr(cs.Ready), toState(cs.State), strconv.Itoa(int(cs.RestartCount))
}
return append(ff,
i.Name,
i.Image,
ready,
state,
restarts,
probe(i.LivenessProbe)+":"+probe(i.ReadinessProbe),
scpu,
smem,
pcpu,
pmem,
toAge(r.pod.CreationTimestamp),
)
}
// ----------------------------------------------------------------------------
// 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 probe(p *v1.Probe) string {
if p == nil {
return "on"
}
return "off"
}
func asMi(v int64) float64 {
return float64(v) / 1024 * 1024
}