dev-pod-api-build/internal/k8s/cluster.go
2026-04-16 04:16:36 +00:00

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
}