Files
yms-daemon/internal/backendstatus/diagnoser.go
T

158 lines
6.3 KiB
Go
Raw Normal View History

// 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 使用
// enginenative 使用 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.Validatestore、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}
}