package handler import ( "context" "encoding/json" "time" "github.com/goodrain/rainbond/api/model" ) const defaultNodeFilesystemStatsTimeout = 3 * time.Second type kubeletFilesystemStats struct { CapacityBytes *uint64 `json:"capacityBytes"` AvailableBytes *uint64 `json:"availableBytes"` UsedBytes *uint64 `json:"usedBytes"` } type kubeletStatsSummary struct { Node struct { Fs *kubeletFilesystemStats `json:"fs"` Runtime struct { ImageFs *kubeletFilesystemStats `json:"imageFs"` ContainerFs *kubeletFilesystemStats `json:"containerFs"` } `json:"runtime"` } `json:"node"` } type filesystemUsage struct { capacityBytes uint64 usedBytes uint64 } type nodeFilesystemStats struct { root filesystemUsage container filesystemUsage } func (n *nodesHandle) getNodeFilesystemStats(ctx context.Context, nodeName string) (nodeFilesystemStats, error) { timeout := n.nodeFilesystemStatsTimeout if timeout <= 0 { timeout = defaultNodeFilesystemStatsTimeout } queryCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() raw, err := n.clientset.CoreV1().RESTClient().Get(). Resource("nodes"). Name(nodeName). SubResource("proxy"). Suffix("stats", "summary"). DoRaw(queryCtx) if err != nil { return nodeFilesystemStats{}, err } return parseNodeFilesystemStats(raw) } func (n *nodesHandle) setNodeFilesystemStats(ctx context.Context, nodeName string, resource *model.Resource) error { stats, err := n.getNodeFilesystemStats(ctx, nodeName) if err != nil { return err } resource.CapDisk = stats.root.capacityBytes resource.ReqDisk = stats.root.usedBytes resource.CapContainerDisk = stats.container.capacityBytes resource.ReqContainerDisk = stats.container.usedBytes return nil } func nodeDiskStatsStatus(resource model.Resource, err error) string { if err != nil || resource.CapDisk == 0 && resource.CapContainerDisk == 0 { return "unavailable" } if resource.CapDisk == 0 || resource.CapContainerDisk == 0 { return "partial" } return "ok" } func parseNodeFilesystemStats(raw []byte) (nodeFilesystemStats, error) { var summary kubeletStatsSummary if err := json.Unmarshal(raw, &summary); err != nil { return nodeFilesystemStats{}, err } return nodeFilesystemStats{ root: filesystemUsageFromKubelet(summary.Node.Fs), container: mostUtilizedFilesystem( filesystemUsageFromKubelet(summary.Node.Runtime.ImageFs), filesystemUsageFromKubelet(summary.Node.Runtime.ContainerFs), ), }, nil } func filesystemUsageFromKubelet(stats *kubeletFilesystemStats) filesystemUsage { if stats == nil || stats.CapacityBytes == nil || *stats.CapacityBytes == 0 { return filesystemUsage{} } capacity := *stats.CapacityBytes if stats.AvailableBytes != nil && *stats.AvailableBytes <= capacity { return filesystemUsage{ capacityBytes: capacity, usedBytes: capacity - *stats.AvailableBytes, } } if stats.UsedBytes == nil { return filesystemUsage{} } used := *stats.UsedBytes if used > capacity { used = capacity } return filesystemUsage{capacityBytes: capacity, usedBytes: used} } func mostUtilizedFilesystem(filesystems ...filesystemUsage) filesystemUsage { var selected filesystemUsage for _, filesystem := range filesystems { if filesystem.capacityBytes == 0 { continue } if selected.capacityBytes == 0 || float64(filesystem.usedBytes)/float64(filesystem.capacityBytes) > float64(selected.usedBytes)/float64(selected.capacityBytes) { selected = filesystem } } return selected }