mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-05-11 18:10:23 +08:00
功能: v2.2 节点池调度 + Grafana Dashboard + 版本漂移 UI (#49)
节点池动态调度(企业集群核心需求): - model.Node 新增 Labels CSV;Node.HasLabel / LabelSet 辅助方法 - model.BackupTask 新增 NodePoolTag;与 NodeID 互斥(校验层拒绝同时设置) - BackupExecutionService.selectPoolNode:匹配标签的在线节点中选"运行中任务最少" 并列按 ID 升序稳定;空池返回 NODE_POOL_EMPTY 让用户立即感知 - 选中节点仅写 BackupRecord,不回写 task.NodeID —— 每次执行重选实现真轮转均衡 Grafana Dashboard(v2.1 指标的可视化闭环): - deploy/grafana/backupx-dashboard.json:11 个面板覆盖概览/时序/容量/集群 - deploy/grafana/README.md:Prometheus 抓取配置 + 告警建议 - release workflow 打包 grafana/ + nginx.conf 到 tar.gz 前端: - 节点列表:Agent 版本 vs Master 不一致时橙红 Tag + Tooltip 提示升级 - 节点列表新增"标签/节点池"列,支持 CSV 编辑 + 并发/带宽一起改 - 任务表单新增 NodePoolTag 输入框,与节点选择器互斥禁用 测试: - model/node_label_test.go:HasLabel / LabelSet / nil 安全 - service/node_pool_scheduler_test.go:负载最低优先 / 空池错误 / nil repo 降级 - go test ./... + npm run build 全绿
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
NodeStatusOnline = "online"
|
||||
@@ -29,8 +32,42 @@ type Node struct {
|
||||
// BandwidthLimit 该节点上传带宽上限(rclone 可识别格式:10M / 1G / 0=不限)。
|
||||
// 对集群感知的上传场景有效(Master 本地与 Agent 运行时均会应用)。
|
||||
BandwidthLimit string `gorm:"column:bandwidth_limit;size:32" json:"bandwidthLimit"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
// Labels 节点标签(CSV,如 "prod,db-host,high-mem")。
|
||||
// 用于任务调度的节点池选择:任务配置 NodePoolTag 时,调度器会从 Labels 包含该 tag 的
|
||||
// 在线节点中自动挑选一台执行(按当前运行中任务数升序)。单节点可属多个池。
|
||||
Labels string `gorm:"column:labels;size:500" json:"labels"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// LabelSet 把 CSV Labels 解析为 set,便于做成员判定。
|
||||
// 空白与空 token 自动忽略。
|
||||
func (n *Node) LabelSet() map[string]struct{} {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]struct{})
|
||||
for _, raw := range strings.Split(n.Labels, ",") {
|
||||
label := strings.TrimSpace(raw)
|
||||
if label != "" {
|
||||
out[label] = struct{}{}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// HasLabel 判断节点是否属于指定池。nil/空 tag 返回 false。
|
||||
func (n *Node) HasLabel(tag string) bool {
|
||||
tag = strings.TrimSpace(tag)
|
||||
if n == nil || tag == "" {
|
||||
return false
|
||||
}
|
||||
for _, raw := range strings.Split(n.Labels, ",") {
|
||||
if strings.TrimSpace(raw) == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (Node) TableName() string {
|
||||
|
||||
Reference in New Issue
Block a user