// Package backendstatus 对 backend 组件的现场状态与 SQLite 事实来源做只读诊断,并按需执行自动修复。 // // 诊断遵循“SQLite 是事实来源、外部系统是待校验状态”的原则:status 与 doctor 只读, // 不修改任何现场;reconcile 默认只生成修复计划,仅在显式要求 apply 时执行经过明确授权的 // 自动修复动作,且每个修复动作都先写入事务再执行外部变更。 package backendstatus import ( "context" "errors" "fmt" "yms-daemon/internal/containerengine" "yms-daemon/internal/daemonapi" "yms-daemon/internal/deploymentconfig" "yms-daemon/internal/hostnginx" "yms-daemon/internal/systemd" "yms-daemon/internal/transaction" ) // gateway 抽象宿主 Nginx 配置的读取与切流,便于在测试中替换。 type gateway interface { // Read 返回当前完整配置快照及其活动后端端口。 Read() (hostnginx.Snapshot, error) // Switch 把活动后端端口切到指定端口并重载 Nginx。 Switch(context.Context, int) (hostnginx.Snapshot, error) } // Diagnoser 持有执行诊断与自动修复所需的全部依赖。 // 除 store、coordinator、gateway 外,其余依赖按组件运行类型选择性使用:container 使用 // engine,native 使用 units,未使用的依赖可以为 nil。 type Diagnoser struct { // config 已校验的本地部署配置,决定组件运行类型与槽位信息。 config deploymentconfig.Config // store 服务端 SQLite 事务存储,是诊断的事实来源。 store *transaction.Store // coordinator 用于在 reconcile --apply 时以可恢复方式执行单个修复步骤。 coordinator *transaction.Coordinator // engine 容器引擎,container 后端诊断时使用。 engine containerengine.Engine // units systemd 管理器,native 后端诊断时使用。 units systemd.Manager // gateway 宿主 Nginx 配置控制器,用于读取活动端口与执行切流修复。 gateway gateway } // New 构造一个 Diagnoser 并校验依赖。 // config 必须已经通过 deploymentconfig.Validate;store、coordinator、gateway 不能为空; // 当 backend.type 为 container 时 engine 不能为空,为 native 时 units 不能为空。 func New(config deploymentconfig.Config, store *transaction.Store, coordinator *transaction.Coordinator, engine containerengine.Engine, units systemd.Manager, gateway gateway) (*Diagnoser, error) { if err := config.Validate(); err != nil { return nil, err } if store == nil || coordinator == nil || gateway == nil { return nil, errors.New("backend status dependencies are required") } switch config.Backend.Type { case deploymentconfig.BackendTypeContainer: if engine == nil { return nil, errors.New("container backend diagnosis requires a container engine") } case deploymentconfig.BackendTypeNative: if units == nil { return nil, errors.New("native backend diagnosis requires a systemd manager") } default: return nil, fmt.Errorf("unsupported backend.type %q", config.Backend.Type) } return &Diagnoser{config: config, store: store, coordinator: coordinator, engine: engine, units: units, gateway: gateway}, nil } // Diagnose 执行只读诊断并返回结构化结果,不修改任何现场状态。 // 返回的 daemonapi.Diagnosis 中,Healthy 为 true 表示不存在需要人工处理的漂移项。 func (d *Diagnoser) Diagnose(ctx context.Context) (daemonapi.Diagnosis, error) { switch d.config.Backend.Type { case deploymentconfig.BackendTypeContainer: return d.diagnoseContainer(ctx) case deploymentconfig.BackendTypeNative: return d.diagnoseNative(ctx) default: return daemonapi.Diagnosis{}, fmt.Errorf("unsupported backend.type %q", d.config.Backend.Type) } } // Reconcile 生成修复计划(apply 为 false)或执行自动修复(apply 为 true)。 // apply 为 false 时行为与 Diagnose 一致,仅返回只读诊断;apply 为 true 时先诊断, // 对每个可自动修复项执行修复动作,完成后重新诊断并返回修复后的结果。 func (d *Diagnoser) Reconcile(ctx context.Context, apply bool) (daemonapi.Diagnosis, error) { if !apply { return d.Diagnose(ctx) } switch d.config.Backend.Type { case deploymentconfig.BackendTypeContainer: return d.reconcileContainer(ctx) case deploymentconfig.BackendTypeNative: return d.reconcileNative(ctx) default: return daemonapi.Diagnosis{}, fmt.Errorf("unsupported backend.type %q", d.config.Backend.Type) } } // otherPort 返回给定端口的另一个蓝绿槽位端口。 func otherPort(port int) int { if port == deploymentconfig.BackendPort8080 { return deploymentconfig.BackendPort8081 } return deploymentconfig.BackendPort8080 } // finding 内部诊断结论,比协议层的 DiagnosisItem 多携带一个可选的修复参数。 type finding struct { // level 诊断级别,取值 daemonapi.DiagnosisLevelOK / DiagnosisLevelFixable / DiagnosisLevelDrift。 level string // code 稳定标识,供测试与 reconcile 识别。 code string // message 人类可读的诊断描述。 message string // action 仅在 level 为 fixable 时给出建议动作描述。 action string // fix 描述可自动修复动作,仅 fixable 项非 nil。 fix *fixAction } // fixAction 描述一个可自动修复动作所需的精确参数。 type fixAction struct { // kind 修复动作种类,取值 fixSwitchPort 或 fixRemoveContainer。 kind string // port 切流修复的目标端口。 port int // container 容器移除修复的目标容器名。 container string } const ( // fixSwitchPort 表示把宿主 Nginx 切流到指定端口。 fixSwitchPort = "nginx-switch" // fixRemoveContainer 表示移除残留的已停止容器。 fixRemoveContainer = "container-remove" ) // toDiagnosis 把内部 finding 列表转换为协议层诊断结果,并据此计算 Healthy 标志。 func toDiagnosis(service string, runtimeType string, findings []finding) daemonapi.Diagnosis { items := make([]daemonapi.DiagnosisItem, 0, len(findings)) healthy := true for _, f := range findings { if f.level == daemonapi.DiagnosisLevelDrift { healthy = false } items = append(items, daemonapi.DiagnosisItem{ Level: f.level, Code: f.code, Message: f.message, Action: f.action, }) } return daemonapi.Diagnosis{Service: service, Type: runtimeType, Healthy: healthy, Items: items} }