166 lines
4.3 KiB
Go
166 lines
4.3 KiB
Go
package k8s
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"github.com/iliaivanov/spec-kit-remote/cmd/dev-pod-api/internal/model"
|
|
)
|
|
|
|
// nodeMetricsList mirrors the metrics API response for nodes.
|
|
type nodeMetricsList struct {
|
|
Items []nodeMetrics `json:"items"`
|
|
}
|
|
|
|
type nodeMetrics struct {
|
|
Metadata struct {
|
|
Name string `json:"name"`
|
|
} `json:"metadata"`
|
|
Usage map[string]string `json:"usage"`
|
|
}
|
|
|
|
// GetClusterStatus returns node list with capacity, allocatable, and usage.
|
|
func (c *Client) GetClusterStatus(ctx context.Context) (*model.ClusterStatus, error) {
|
|
nodes, err := c.Clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list nodes: %w", err)
|
|
}
|
|
|
|
// Try to fetch node metrics (may not be available)
|
|
metricsMap := c.fetchNodeMetrics(ctx)
|
|
|
|
var totalCPUCap, totalCPUAlloc, totalMemCap, totalMemAlloc int64
|
|
status := &model.ClusterStatus{
|
|
Nodes: make([]model.NodeStatus, 0, len(nodes.Items)),
|
|
}
|
|
|
|
for _, node := range nodes.Items {
|
|
ns := model.NodeStatus{
|
|
Name: node.Name,
|
|
Status: nodeConditionStatus(node),
|
|
CPUCapacity: node.Status.Capacity.Cpu().String(),
|
|
CPUAllocatable: node.Status.Allocatable.Cpu().String(),
|
|
MemCapacity: node.Status.Capacity.Memory().String(),
|
|
MemAllocatable: node.Status.Allocatable.Memory().String(),
|
|
}
|
|
|
|
totalCPUCap += node.Status.Capacity.Cpu().MilliValue()
|
|
totalCPUAlloc += node.Status.Allocatable.Cpu().MilliValue()
|
|
totalMemCap += node.Status.Capacity.Memory().Value()
|
|
totalMemAlloc += node.Status.Allocatable.Memory().Value()
|
|
|
|
if m, ok := metricsMap[node.Name]; ok {
|
|
ns.CPUUsage = m.cpuUsage
|
|
ns.MemUsage = m.memUsage
|
|
}
|
|
|
|
status.Nodes = append(status.Nodes, ns)
|
|
}
|
|
|
|
status.Total = model.ResourceSummary{
|
|
CPUCapacity: resource.NewMilliQuantity(totalCPUCap, resource.DecimalSI).String(),
|
|
CPUAllocatable: resource.NewMilliQuantity(totalCPUAlloc, resource.DecimalSI).String(),
|
|
MemCapacity: resource.NewQuantity(totalMemCap, resource.BinarySI).String(),
|
|
MemAllocatable: resource.NewQuantity(totalMemAlloc, resource.BinarySI).String(),
|
|
}
|
|
|
|
return status, nil
|
|
}
|
|
|
|
type nodeUsage struct {
|
|
cpuUsage string
|
|
memUsage string
|
|
}
|
|
|
|
func (c *Client) fetchNodeMetrics(ctx context.Context) map[string]nodeUsage {
|
|
result := make(map[string]nodeUsage)
|
|
|
|
restClient := c.Clientset.Discovery().RESTClient()
|
|
if restClient == nil {
|
|
return result
|
|
}
|
|
|
|
data, err := restClient.Get().
|
|
AbsPath("/apis/metrics.k8s.io/v1beta1/nodes").
|
|
DoRaw(ctx)
|
|
if err != nil {
|
|
return result
|
|
}
|
|
|
|
var metrics nodeMetricsList
|
|
if err := json.Unmarshal(data, &metrics); err != nil {
|
|
return result
|
|
}
|
|
|
|
for _, m := range metrics.Items {
|
|
nu := nodeUsage{}
|
|
if cpu, ok := m.Usage["cpu"]; ok {
|
|
nu.cpuUsage = cpu
|
|
}
|
|
if mem, ok := m.Usage["memory"]; ok {
|
|
nu.memUsage = mem
|
|
}
|
|
result[m.Metadata.Name] = nu
|
|
}
|
|
return result
|
|
}
|
|
|
|
func nodeConditionStatus(node corev1.Node) string {
|
|
for _, cond := range node.Status.Conditions {
|
|
if cond.Type == corev1.NodeReady {
|
|
if cond.Status == corev1.ConditionTrue {
|
|
return "Ready"
|
|
}
|
|
return "NotReady"
|
|
}
|
|
}
|
|
return "Unknown"
|
|
}
|
|
|
|
// cacheServices maps friendly names to PVC names in the dev-infra namespace.
|
|
var cacheServices = []struct {
|
|
name string
|
|
pvcName string
|
|
}{
|
|
{"verdaccio", "verdaccio-storage"},
|
|
{"athens", "athens-storage"},
|
|
{"cargo-proxy", "cargo-proxy-cache"},
|
|
}
|
|
|
|
// GetCacheStats returns PVC status for cache services in dev-infra.
|
|
func (c *Client) GetCacheStats(ctx context.Context) ([]model.CacheStat, error) {
|
|
stats := make([]model.CacheStat, 0, len(cacheServices))
|
|
|
|
for _, svc := range cacheServices {
|
|
pvc, err := c.Clientset.CoreV1().PersistentVolumeClaims("dev-infra").Get(ctx, svc.pvcName, metav1.GetOptions{})
|
|
if err != nil {
|
|
stats = append(stats, model.CacheStat{
|
|
Name: svc.name,
|
|
PVCName: svc.pvcName,
|
|
Status: "NotFound",
|
|
})
|
|
continue
|
|
}
|
|
|
|
capacity := ""
|
|
if pvc.Status.Capacity != nil {
|
|
if storage, ok := pvc.Status.Capacity[corev1.ResourceStorage]; ok {
|
|
capacity = storage.String()
|
|
}
|
|
}
|
|
|
|
stats = append(stats, model.CacheStat{
|
|
Name: svc.name,
|
|
PVCName: svc.pvcName,
|
|
Capacity: capacity,
|
|
Status: string(pvc.Status.Phase),
|
|
})
|
|
}
|
|
|
|
return stats, nil
|
|
}
|