Ctrl K
ComunidadGuías…

api/handler/cluster_overview_resources.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"runtime"
	"time"

	"github.com/goodrain/rainbond/api/model"
	"github.com/shirou/gopsutil/v4/disk"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const clusterInfoQueryTimeout = 3 * time.Second

const clusterLocalDiskSampleTTL = 30 * time.Second

type clusterInfoRefresh struct {
	done   chan struct{}
	result *model.ClusterResource
	err    error
}

type clusterLocalDiskSample struct {
	capacity, used         uint64
	available, failed      bool
	refreshing             bool
	sampledAt, lastAttempt time.Time
}

// loadClusterInfo uses one Node list and one workload Pod list, independent of
// exporter availability. Requests are scheduled Rainbond workloads, not actual
// usage, and node capacity remains the whole cluster's allocatable capacity.
func (c *clusterAction) loadClusterInfo(ctx context.Context) (*model.ClusterResource, error) {
	nodes, err := c.listNodes(ctx)
	if err != nil {
		return nil, fmt.Errorf("get cluster nodes: %w", err)
	}
	pods, err := c.clientset.CoreV1().Pods(metav1.NamespaceAll).List(ctx, metav1.ListOptions{LabelSelector: "creator=Rainbond"})
	if err != nil {
		return nil, fmt.Errorf("get cluster workload pods: %w", err)
	}
	result := buildClusterResourceSnapshot(nodes, pods.Items)

	// Version is optional and has a shorter deadline than the resource reads.
	versionCtx, cancel := context.WithTimeout(ctx, 250*time.Millisecond)
	defer cancel()
	if raw, err := c.clientset.Discovery().RESTClient().Get().AbsPath("/version").DoRaw(versionCtx); err == nil {
		var version struct {
			GitVersion string `json:"gitVersion"`
		}
		if json.Unmarshal(raw, &version) == nil {
			result.K8sVersion = version.GitVersion
		}
	}
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	return result, nil
}

// withLocalDiskStats returns a copy, so background sampling never mutates a
// Kubernetes snapshot or a response already being encoded. Unix statfs cannot
// be canceled: retain at most one background call, including if it hangs.
func (c *clusterAction) withLocalDiskStats(result *model.ClusterResource) *model.ClusterResource {
	if result == nil {
		return nil
	}
	response := *result
	response.LocalDiskStatsStatus = "unavailable"
	now := time.Now()
	c.cacheMu.Lock()
	sample := c.localDisk
	if sample.available {
		response.CapDisk, response.ReqDisk = sample.capacity, sample.used
		response.LocalDiskStatsStatus = "ok"
		if sample.failed || !sample.sampledAt.Add(clusterLocalDiskSampleTTL).After(now) {
			response.LocalDiskStatsStatus = "stale"
		}
	}
	refreshAfter := sample.lastAttempt.Add(clusterLocalDiskSampleTTL)
	if sample.available && !sample.failed {
		refreshAfter = sample.sampledAt.Add(clusterLocalDiskSampleTTL)
	}
	if !sample.refreshing && !refreshAfter.After(now) {
		c.localDisk.refreshing = true
		c.localDisk.lastAttempt = now
		readDisk := c.diskUsage
		if readDisk == nil {
			readDisk = disk.Usage
		}
		go c.refreshLocalDisk(readDisk)
	}
	c.cacheMu.Unlock()
	return &response
}

func (c *clusterAction) refreshLocalDisk(readDisk func(string) (*disk.UsageStat, error)) {
	path := "/grdata"
	if runtime.GOOS == "windows" {
		path = `z:\\`
	}
	usage, err := readDisk(path)
	c.cacheMu.Lock()
	defer c.cacheMu.Unlock()
	c.localDisk.refreshing = false
	c.localDisk.failed = err != nil || usage == nil || usage.Total == 0 || usage.Used > usage.Total
	if !c.localDisk.failed {
		c.localDisk.capacity, c.localDisk.used = usage.Total, usage.Used
		c.localDisk.sampledAt = time.Now()
		c.localDisk.available = true
	}
}

func buildClusterResourceSnapshot(nodes []*corev1.Node, pods []corev1.Pod) *model.ClusterResource {
	result := &model.ClusterResource{
		AllNode: len(nodes), ComputeNode: len(nodes), ResourceProxyStatus: true,
		ResourceSource: "kubernetes", ResourceScope: "rainbond_workloads",
	}
	nodeMap := make(map[string]*corev1.Node, len(nodes))
	resources := make(map[string]*model.NodeResource, len(nodes))
	for _, node := range nodes {
		nodeMap[node.Name] = node
		resources[node.Name] = model.NewResource(node.Status.Allocatable)
		result.CapCPUMillicores += node.Status.Allocatable.Cpu().MilliValue()
	}
	var healthyCPU, unhealthyCPU, healthyMemory, unhealthyMemory int64
	for i := range pods {
		pod := &pods[i]
		node := nodeMap[pod.Spec.NodeName]
		if node == nil || isTerminalPod(pod) || pod.Labels["creator"] != "Rainbond" {
			continue
		}
		info := podResourceInformationFromPod(pod)
		result.RunPodNumber++ // Legacy: scheduled non-terminal workloads.
		if pod.Status.Phase == corev1.PodRunning {
			result.RunningPodNumber++
		}
		if isNodeReady(node) {
			healthyCPU += info.CPU
			healthyMemory += info.Memory
		} else {
			unhealthyCPU += info.CPU
			unhealthyMemory += info.Memory
		}
		available := resources[node.Name]
		available.AllowedPodNumber--
		available.MilliCPU -= info.CPU
		available.Memory -= info.Memory
		available.EphemeralStorage -= info.StorageEphemeral
	}
	var healthCapMemory, unhealthCapMemory int64
	for _, node := range nodes {
		if isNodeReady(node) {
			result.NodeReady++
			result.HealthCapCPU += int(node.Status.Allocatable.Cpu().Value())
			healthCapMemory += node.Status.Allocatable.Memory().Value()
		} else {
			result.NotReadyNode++
			result.UnhealthCapCPU += int(node.Status.Allocatable.Cpu().Value())
			unhealthCapMemory += node.Status.Allocatable.Memory().Value()
		}
		if !isNodeReady(node) || node.Spec.Unschedulable || containsTaints(node) {
			continue
		}
		available := resources[node.Name]
		if result.MaxAllocatableMemoryNodeResource == nil || available.Memory > result.MaxAllocatableMemoryNodeResource.Memory {
			result.MaxAllocatableMemoryNodeResource = available
		}
	}
	result.CapCPU = result.HealthCapCPU + result.UnhealthCapCPU
	result.CapMem = int((healthCapMemory + unhealthCapMemory) / (1024 * 1024))
	result.HealthCapMem = int(healthCapMemory / (1024 * 1024))
	result.UnhealthCapMem = int(unhealthCapMemory / (1024 * 1024))
	result.HealthReqCPU = float32(healthyCPU) / 1000
	result.UnhealthReqCPU = float32(unhealthyCPU) / 1000
	result.ReqCPU = float32(healthyCPU+unhealthyCPU) / 1000
	result.HealthReqMem = int(healthyMemory / (1024 * 1024))
	result.UnhealthReqMem = int(unhealthyMemory / (1024 * 1024))
	result.ReqMem = int((healthyMemory + unhealthyMemory) / (1024 * 1024))
	result.RainbondReqCPU = result.ReqCPU
	result.RainbondReqMem = result.ReqMem
	return result
}