Files

158 lines
6.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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}
}