k9s/internal/model/node.go

153 lines
3.4 KiB
Go

package model
import (
"context"
"fmt"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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 _ render.NodeWithMetrics = &NodeWithMetrics{}
// Node represents a node model.
type Node struct {
Resource
}
// List returns a collection of node resources.
func (n *Node) List(_ context.Context) ([]runtime.Object, error) {
nn, err := n.factory.Client().DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{})
if err != nil {
return nil, err
}
oo := make([]runtime.Object, len(nn.Items))
for i := range nn.Items {
o, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&nn.Items[i])
if err != nil {
return nil, err
}
oo[i] = &unstructured.Unstructured{Object: o}
}
return oo, nil
}
func nameFromMeta(m map[string]interface{}) string {
meta, ok := m["metadata"].(map[string]interface{})
if !ok {
return "n/a"
}
name, ok := meta["name"].(string)
if !ok {
return "n/a"
}
return name
}
// Hydrate returns nodes as rows.
func (n *Node) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error {
mx := client.NewMetricsServer(n.factory.Client())
mmx, err := mx.FetchNodesMetrics()
if err != nil {
log.Warn().Err(err).Msg("No node metrics")
}
for i, o := range oo {
no, ok := o.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("expecting unstructured but got %T", o)
}
pods, err := n.nodePods(n.factory, nameFromMeta(no.Object))
if err != nil {
return err
}
var (
row render.Row
nmx = NodeWithMetrics{
object: no,
mx: nodeMetricsFor(o, mmx),
pods: pods,
}
)
if err := re.Render(&nmx, "", &row); err != nil {
return err
}
rr[i] = row
}
return nil
}
func nodeMetricsFor(o runtime.Object, mmx *mv1beta1.NodeMetricsList) *mv1beta1.NodeMetrics {
fqn := extractFQN(o)
for _, mx := range mmx.Items {
if MetaFQN(mx.ObjectMeta) == fqn {
return &mx
}
}
return nil
}
func (n *Node) nodePods(f Factory, node string) ([]*v1.Pod, error) {
pp, err := f.List("v1/pods", render.AllNamespaces, labels.Everything())
if err != nil {
return nil, err
}
pods := make([]*v1.Pod, 0, len(pp))
for _, p := range pp {
o, ok := p.(*unstructured.Unstructured)
if !ok {
return nil, fmt.Errorf("expecting unstructured but got %T", p)
}
var pod v1.Pod
err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.Object, &pod)
if err != nil {
log.Error().Err(err).Msg("Converting Pod")
return nil, err
}
if pod.Spec.NodeName != node || pod.Status.Phase != v1.PodSucceeded {
continue
}
pods = append(pods, &pod)
}
return pods, nil
}
// ----------------------------------------------------------------------------
// Helpers...
// NodeWithMetrics represents a node with its associated metrics.
type NodeWithMetrics struct {
object runtime.Object
mx *mv1beta1.NodeMetrics
pods []*v1.Pod
}
// Object returns a node.
func (n *NodeWithMetrics) Object() runtime.Object {
return n.object
}
// Metrics returns the node metrics.
func (n *NodeWithMetrics) Metrics() *mv1beta1.NodeMetrics {
return n.mx
}
// Pods return pods running on this node.
func (n *NodeWithMetrics) Pods() []*v1.Pod {
return n.pods
}