feat: wire delivery result and infra data backends
This commit is contained in:
@@ -47,9 +47,38 @@ type ContainerWorkload struct {
|
||||
StatusClass string `json:"statusClass"`
|
||||
}
|
||||
|
||||
type ContainerCluster struct {
|
||||
Name string `json:"name"`
|
||||
Zone string `json:"zone"`
|
||||
ZoneClass string `json:"zoneClass"`
|
||||
Status string `json:"status"`
|
||||
StatusClass string `json:"statusClass"`
|
||||
Nodes int `json:"nodes"`
|
||||
ReadyNodes int `json:"readyNodes"`
|
||||
CPU int `json:"cpu"`
|
||||
CPUClass string `json:"cpuClass"`
|
||||
Version string `json:"version"`
|
||||
Calico string `json:"calico"`
|
||||
}
|
||||
|
||||
type ContainerNode struct {
|
||||
Cluster string `json:"cluster"`
|
||||
Name string `json:"name"`
|
||||
IP string `json:"ip"`
|
||||
Label string `json:"label"`
|
||||
Taint string `json:"taint"`
|
||||
Spec string `json:"spec"`
|
||||
CPU int `json:"cpu"`
|
||||
CPUClass string `json:"cpuClass"`
|
||||
Status string `json:"status"`
|
||||
StatusClass string `json:"statusClass"`
|
||||
}
|
||||
|
||||
type ContainerServiceData struct {
|
||||
Summary ContainerServiceSummary `json:"summary"`
|
||||
Workloads []ContainerWorkload `json:"items"`
|
||||
Clusters []ContainerCluster `json:"clusters"`
|
||||
Nodes []ContainerNode `json:"nodes"`
|
||||
}
|
||||
|
||||
type ContainerBusinessLineQuery struct {
|
||||
@@ -81,7 +110,7 @@ func (s *WayneRoleBindingService) ContainerServiceData(ctx context.Context, quer
|
||||
Namespaces: len(query.Namespaces),
|
||||
}
|
||||
workloads := make([]ContainerWorkload, 0)
|
||||
clusterNodes := map[string]wayneNodeSummary{}
|
||||
clusterNodes := map[string]wayneNodeData{}
|
||||
seenWorkloads := map[string]struct{}{}
|
||||
errors := make([]string, 0)
|
||||
|
||||
@@ -142,7 +171,9 @@ func (s *WayneRoleBindingService) ContainerServiceData(ctx context.Context, quer
|
||||
}
|
||||
}
|
||||
|
||||
for _, nodes := range clusterNodes {
|
||||
clusters := make([]ContainerCluster, 0, len(clusterNodes))
|
||||
allNodes := make([]ContainerNode, 0)
|
||||
for clusterName, nodes := range clusterNodes {
|
||||
summary.Nodes += nodes.Total
|
||||
summary.ReadyNodes += nodes.Ready
|
||||
summary.SchedulableNodes += nodes.Schedulable
|
||||
@@ -150,6 +181,10 @@ func (s *WayneRoleBindingService) ContainerServiceData(ctx context.Context, quer
|
||||
summary.CPUTotal += nodes.CPUTotal
|
||||
summary.MemoryUsed += nodes.MemoryUsed
|
||||
summary.MemoryTotal += nodes.MemoryTotal
|
||||
clusters = append(clusters, containerClusterFromWayne(clusterName, nodes))
|
||||
for _, node := range nodes.Nodes {
|
||||
allNodes = append(allNodes, containerNodeFromWayne(clusterName, node, query.BusinessLineName))
|
||||
}
|
||||
}
|
||||
summary.Clusters = len(clusterNodes)
|
||||
summary.Workloads = len(workloads)
|
||||
@@ -157,7 +192,9 @@ func (s *WayneRoleBindingService) ContainerServiceData(ctx context.Context, quer
|
||||
summary.MemoryPercent = ratioPercent(summary.MemoryUsed, summary.MemoryTotal)
|
||||
summary.Errors = errors
|
||||
|
||||
return &ContainerServiceData{Summary: summary, Workloads: workloads}, nil
|
||||
sortContainerClusters(clusters)
|
||||
sortContainerNodes(allNodes)
|
||||
return &ContainerServiceData{Summary: summary, Workloads: workloads, Clusters: clusters, Nodes: allNodes}, nil
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) ListDeploymentHistories(ctx context.Context, namespaces []model.BusinessLineWayneNamespace, limit int) ([]WayneDeploymentHistory, error) {
|
||||
@@ -264,10 +301,10 @@ func (s *WayneRoleBindingService) listWaynePublishHistories(ctx context.Context,
|
||||
return parseWaynePublishHistories(result.Body)
|
||||
}
|
||||
|
||||
func (s *WayneRoleBindingService) getWayneNodes(ctx context.Context, cluster string) (wayneNodeSummary, error) {
|
||||
func (s *WayneRoleBindingService) getWayneNodes(ctx context.Context, cluster string) (wayneNodeData, error) {
|
||||
result, err := s.callRaw(ctx, http.MethodGet, fmt.Sprintf("/api/v1/kubernetes/nodes/clusters/%s", url.PathEscape(cluster)), nil)
|
||||
if err != nil {
|
||||
return wayneNodeSummary{}, err
|
||||
return wayneNodeData{}, err
|
||||
}
|
||||
return parseWayneNodeSummary(result.Body)
|
||||
}
|
||||
@@ -323,7 +360,7 @@ type wayneDeployment struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type wayneNodeSummary struct {
|
||||
type wayneNodeData struct {
|
||||
Total int
|
||||
Ready int
|
||||
Schedulable int
|
||||
@@ -331,6 +368,23 @@ type wayneNodeSummary struct {
|
||||
CPUTotal float64
|
||||
MemoryUsed float64
|
||||
MemoryTotal float64
|
||||
Nodes []wayneNode
|
||||
}
|
||||
|
||||
type wayneNode struct {
|
||||
Name string `json:"name"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
Spec struct {
|
||||
Unschedulable bool `json:"unschedulable"`
|
||||
Taints []map[string]any `json:"taints"`
|
||||
Ready string `json:"ready"`
|
||||
} `json:"spec"`
|
||||
Status struct {
|
||||
Capacity map[string]string `json:"capacity"`
|
||||
NodeInfo struct {
|
||||
KubeletVersion string `json:"kubeletVersion"`
|
||||
} `json:"nodeInfo"`
|
||||
} `json:"status"`
|
||||
}
|
||||
|
||||
func parseWayneNamespaceDetail(body []byte) (wayneNamespaceDetail, error) {
|
||||
@@ -420,7 +474,7 @@ func sortWayneDeploymentHistories(items []WayneDeploymentHistory) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseWayneNodeSummary(body []byte) (wayneNodeSummary, error) {
|
||||
func parseWayneNodeSummary(body []byte) (wayneNodeData, error) {
|
||||
var wrapped struct {
|
||||
Data struct {
|
||||
NodeSummary struct {
|
||||
@@ -436,12 +490,13 @@ func parseWayneNodeSummary(body []byte) (wayneNodeSummary, error) {
|
||||
Total float64 `json:"total"`
|
||||
Used float64 `json:"used"`
|
||||
} `json:"memorySummary"`
|
||||
Nodes []wayneNode `json:"nodes"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &wrapped); err != nil {
|
||||
return wayneNodeSummary{}, err
|
||||
return wayneNodeData{}, err
|
||||
}
|
||||
return wayneNodeSummary{
|
||||
return wayneNodeData{
|
||||
Total: wrapped.Data.NodeSummary.Total,
|
||||
Ready: wrapped.Data.NodeSummary.Ready,
|
||||
Schedulable: wrapped.Data.NodeSummary.Schedulable,
|
||||
@@ -449,9 +504,159 @@ func parseWayneNodeSummary(body []byte) (wayneNodeSummary, error) {
|
||||
CPUTotal: wrapped.Data.CPUSummary.Total,
|
||||
MemoryUsed: wrapped.Data.MemorySummary.Used,
|
||||
MemoryTotal: wrapped.Data.MemorySummary.Total,
|
||||
Nodes: wrapped.Data.Nodes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func containerClusterFromWayne(name string, data wayneNodeData) ContainerCluster {
|
||||
status := "健康"
|
||||
statusClass := "ok"
|
||||
if data.Total == 0 {
|
||||
status = "无节点"
|
||||
statusClass = "idle"
|
||||
} else if data.Ready < data.Total {
|
||||
status = strconv.Itoa(data.Total-data.Ready) + " 节点告警"
|
||||
statusClass = "warn"
|
||||
}
|
||||
cpu := ratioPercent(data.CPUUsed, data.CPUTotal)
|
||||
version := "-"
|
||||
for _, node := range data.Nodes {
|
||||
if strings.TrimSpace(node.Status.NodeInfo.KubeletVersion) != "" {
|
||||
version = strings.TrimSpace(node.Status.NodeInfo.KubeletVersion)
|
||||
break
|
||||
}
|
||||
}
|
||||
return ContainerCluster{
|
||||
Name: name,
|
||||
Zone: "-",
|
||||
Status: status,
|
||||
StatusClass: statusClass,
|
||||
Nodes: data.Total,
|
||||
ReadyNodes: data.Ready,
|
||||
CPU: cpu,
|
||||
CPUClass: warnClass(cpu),
|
||||
Version: version,
|
||||
Calico: "-",
|
||||
}
|
||||
}
|
||||
|
||||
func containerNodeFromWayne(cluster string, node wayneNode, businessLineName string) ContainerNode {
|
||||
cpu := 0
|
||||
if node.Status.Capacity != nil {
|
||||
cpu, _ = strconv.Atoi(strings.TrimSpace(node.Status.Capacity["cpu"]))
|
||||
}
|
||||
memory := strings.TrimSpace(node.Status.Capacity["memory"])
|
||||
spec := "-"
|
||||
if cpu > 0 && memory != "" {
|
||||
spec = fmt.Sprintf("%dC/%sG", cpu, memory)
|
||||
} else if cpu > 0 {
|
||||
spec = fmt.Sprintf("%dC", cpu)
|
||||
} else if memory != "" {
|
||||
spec = memory + "G"
|
||||
}
|
||||
ready := strings.EqualFold(strings.TrimSpace(node.Spec.Ready), "true")
|
||||
status := "Ready"
|
||||
statusClass := "ok"
|
||||
if !ready {
|
||||
status = "NotReady"
|
||||
statusClass = "warn"
|
||||
} else if node.Spec.Unschedulable {
|
||||
status = "Ready · 不可调度"
|
||||
statusClass = "warn"
|
||||
}
|
||||
label := businessLineLabel(node.Labels, businessLineName)
|
||||
return ContainerNode{
|
||||
Cluster: cluster,
|
||||
Name: node.Name,
|
||||
IP: firstNodeIP(node.Labels),
|
||||
Label: label,
|
||||
Taint: formatNodeTaints(node.Spec.Taints),
|
||||
Spec: spec,
|
||||
CPU: 0,
|
||||
CPUClass: "",
|
||||
Status: status,
|
||||
StatusClass: statusClass,
|
||||
}
|
||||
}
|
||||
|
||||
func warnClass(value int) string {
|
||||
if value >= 80 {
|
||||
return "warn"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func businessLineLabel(labels map[string]string, businessLineName string) string {
|
||||
for _, key := range []string{"business-line", "business_line", "xinfra/business-line"} {
|
||||
if value := strings.TrimSpace(labels[key]); value != "" {
|
||||
return key + "=" + value
|
||||
}
|
||||
}
|
||||
if businessLineName != "" {
|
||||
return "business-line=" + businessLineName
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
|
||||
func firstNodeIP(labels map[string]string) string {
|
||||
for _, key := range []string{"kubernetes.io/hostname", "internal-ip", "node-ip"} {
|
||||
if value := strings.TrimSpace(labels[key]); value != "" && strings.Contains(value, ".") {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
|
||||
func formatNodeTaints(taints []map[string]any) string {
|
||||
if len(taints) == 0 {
|
||||
return "-"
|
||||
}
|
||||
parts := make([]string, 0, len(taints))
|
||||
for _, taint := range taints {
|
||||
key := strings.TrimSpace(stringFromAny(taint["key"]))
|
||||
value := strings.TrimSpace(stringFromAny(taint["value"]))
|
||||
effect := strings.TrimSpace(stringFromAny(taint["effect"]))
|
||||
text := key
|
||||
if value != "" {
|
||||
text += "=" + value
|
||||
}
|
||||
if effect != "" {
|
||||
text += ":" + effect
|
||||
}
|
||||
if text != "" {
|
||||
parts = append(parts, text)
|
||||
}
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
return "-"
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
|
||||
func sortContainerClusters(items []ContainerCluster) {
|
||||
for i := 1; i < len(items); i++ {
|
||||
item := items[i]
|
||||
j := i - 1
|
||||
for j >= 0 && items[j].Name > item.Name {
|
||||
items[j+1] = items[j]
|
||||
j--
|
||||
}
|
||||
items[j+1] = item
|
||||
}
|
||||
}
|
||||
|
||||
func sortContainerNodes(items []ContainerNode) {
|
||||
for i := 1; i < len(items); i++ {
|
||||
item := items[i]
|
||||
j := i - 1
|
||||
for j >= 0 && (items[j].Cluster > item.Cluster || (items[j].Cluster == item.Cluster && items[j].Name > item.Name)) {
|
||||
items[j+1] = items[j]
|
||||
j--
|
||||
}
|
||||
items[j+1] = item
|
||||
}
|
||||
}
|
||||
|
||||
func containerWorkloadFromMap(item map[string]any, cluster string, namespace string, businessLineName string) ContainerWorkload {
|
||||
name := firstString(item, "name", "objectMeta.name", "metadata.name")
|
||||
ready := firstInt(item, "pods.current", "readyReplicas", "status.readyReplicas", "availableReplicas", "status.availableReplicas")
|
||||
|
||||
Reference in New Issue
Block a user