feat(scheduler): lifecycle 真实接线适配器(2/2) + 装配 BuildRealConfig

把 scheduler 的 stub lifecycle 换成 SQL 后端真实实现,并接入 server:
- SQLLifecycle:MySQL 后端,同时满足 detect.LifecycleService 与
  orchestrate.LifecycleService(两个 thin adapter 包装共享核心)。
  - TransitionStatus:乐观锁 UPDATE nodes SET status WHERE id AND status=from
    → rows affected(1=成功/0=冲突 no-op)+ 写 node_events(按状态映射 enum,
    eventForStatus 纯函数已单测)+ bump directory_version,全在一个事务内。
  - ListNodes/GetNode/SetWeight/BumpVersion/GetLoad(读 LoadCache)/WriteAuditLog。
  - 已知限制:无历史 load 表 → GetLoadHistory 返回当前点(掉量规则安全降级);
    destroy 的 cert 撤销留 TODO(provision 已拆 VM,撤销为纵深防御,待机群再接)。
- BuildRealConfig:用真实 lifecycle + provisionAdapter 装配三循环。
- main.go:SCHED_ENABLED 且有 DB 时走 BuildRealConfig(provision 无厂商凭证则
  CreateNode 优雅失败、替换保持 pending),否则回退 stub。
- 全量 server 23 包测试通过;e2e(判封→drain→换机→置备)待机群+厂商凭证验证。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-17 09:20:40 +08:00
parent 5cc63f9e28
commit 5f3f4189e5
4 changed files with 420 additions and 8 deletions
+26 -4
View File
@@ -34,6 +34,8 @@ import (
"github.com/wangjia/pangolin/server/internal/mtls"
"github.com/wangjia/pangolin/server/internal/nodes"
agentv1 "github.com/wangjia/pangolin/server/internal/pb/agentv1"
"github.com/wangjia/pangolin/server/internal/provision"
"github.com/wangjia/pangolin/server/internal/provision/providers"
"github.com/wangjia/pangolin/server/internal/redisutil"
"github.com/wangjia/pangolin/server/internal/scheduler"
"github.com/wangjia/pangolin/server/internal/scheduler/probe"
@@ -164,9 +166,29 @@ func main() {
if ps == nil {
ps = probe.NewStore(schedRDB)
}
cfg := scheduler.BuildStubConfig(schedRDB, ps)
// TODO(#5): replace stub lifecycle with real LifecycleService
// TODO(#14): replace stub provision with real ProvisionService
// Prefer real lifecycle (SQL) + provision (#14) wiring when a DB is
// available; fall back to no-op stubs otherwise. Without vendor
// credentials the provision service's CreateNode fails gracefully
// (pending replacements stay pending) while lifecycle reads/transitions
// still operate on real node rows.
var cfg scheduler.Config
if sqlDB != nil {
provStore := provision.NewMySQLStore(sqlDB)
provSvc, perr := provision.NewService(provision.Config{
Store: provStore,
Adapters: providers.NewRegistry(),
})
if perr != nil {
log.Printf("scheduler: provision service init failed (%v) — using stub config", perr)
cfg = scheduler.BuildStubConfig(schedRDB, ps)
} else {
cfg = scheduler.BuildRealConfig(schedRDB, ps, sqlDB,
nodes.NewLoadCache(schedRDB), provSvc, provStore)
log.Printf("scheduler: real lifecycle + provision wired")
}
} else {
cfg = scheduler.BuildStubConfig(schedRDB, ps)
}
sched := scheduler.New(cfg)
// sigCtx cancels on SIGINT/SIGTERM, giving the scheduler up to
@@ -179,7 +201,7 @@ func main() {
slog.Error("scheduler: Start error", "error", err)
}
}()
log.Printf("scheduler started (SCHED_ENABLED=true, stub lifecycle/provision)")
log.Printf("scheduler started (SCHED_ENABLED=true)")
}
}