153 lines
3.4 KiB
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, n := range nn.Items {
|
|
o, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&n)
|
|
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
|
|
}
|