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

138 lines
4.3 KiB
Go
Raw Normal View History

package backendstatus
import (
"context"
"errors"
"fmt"
"os"
"yms-daemon/internal/daemonapi"
"yms-daemon/internal/deploymentconfig"
"yms-daemon/internal/systemd"
)
// 诊断项稳定标识,供测试与 reconcile 识别,不参与展示。
const (
codeNativeActivePortOK = "native_nginx_active_port"
codeNativeActiveUnitOK = "native_active_unit_running"
codeNativeActiveUnitDown = "native_active_unit_not_running"
codeNativeInactiveUnitOK = "native_inactive_unit_stopped"
codeNativeInactiveRunning = "native_inactive_unit_running"
codeNativeSlotJAROK = "native_slot_jar_link"
codeNativeSlotJARDrift = "native_slot_jar_link_drift"
)
// diagnoseNative 对 native 后端做只读诊断。
// native 没有部署单例记录,事实来源是宿主 Nginx 活动端口与 systemd 单元现场状态。
func (d *Diagnoser) diagnoseNative(ctx context.Context) (daemonapi.Diagnosis, error) {
findings, err := d.nativeGather(ctx)
if err != nil {
return daemonapi.Diagnosis{}, err
}
return toDiagnosis("backend", deploymentconfig.BackendTypeNative, findings), nil
}
// nativeGather 生成 native 后端的全部诊断项。
func (d *Diagnoser) nativeGather(ctx context.Context) ([]finding, error) {
before, err := d.gateway.Read()
if err != nil {
return nil, fmt.Errorf("read host Nginx configuration: %w", err)
}
activePort := before.ActivePort
findings := make([]finding, 0, 6)
findings = append(findings, finding{
level: daemonapi.DiagnosisLevelOK,
code: codeNativeActivePortOK,
message: fmt.Sprintf("宿主 Nginx 指向后端端口 %d", activePort),
})
activeSlot, err := d.config.Backend.SlotForPort(activePort)
if err != nil {
return nil, err
}
inactiveSlot, err := d.config.Backend.SlotForPort(otherPort(activePort))
if err != nil {
return nil, err
}
activeUnit, err := d.units.Inspect(ctx, activeSlot.Unit)
if err != nil {
return nil, fmt.Errorf("inspect native active unit %s: %w", activeSlot.Unit, err)
}
if unitRunning(activeUnit) {
findings = append(findings, finding{
level: daemonapi.DiagnosisLevelOK,
code: codeNativeActiveUnitOK,
message: "活动单元 " + activeSlot.Unit + " 正在运行",
})
} else {
findings = append(findings, finding{
level: daemonapi.DiagnosisLevelDrift,
code: codeNativeActiveUnitDown,
message: "活动单元 " + activeSlot.Unit + " 未运行(ActiveState=" + activeUnit.ActiveState + "",
})
}
inactiveUnit, err := d.units.Inspect(ctx, inactiveSlot.Unit)
if err != nil {
return nil, fmt.Errorf("inspect native inactive unit %s: %w", inactiveSlot.Unit, err)
}
if unitRunning(inactiveUnit) {
findings = append(findings, finding{
level: daemonapi.DiagnosisLevelDrift,
code: codeNativeInactiveRunning,
message: "非活动单元 " + inactiveSlot.Unit + " 意外运行,两个槽位同时在线",
})
} else {
findings = append(findings, finding{
level: daemonapi.DiagnosisLevelOK,
code: codeNativeInactiveUnitOK,
message: "非活动单元 " + inactiveSlot.Unit + " 已停止",
})
}
if ok, detail := jarLinkStatus(activeSlot.JAR); ok {
findings = append(findings, finding{
level: daemonapi.DiagnosisLevelOK,
code: codeNativeSlotJAROK,
message: "活动槽位 JAR 链接有效:" + detail,
})
} else {
findings = append(findings, finding{
level: daemonapi.DiagnosisLevelDrift,
code: codeNativeSlotJARDrift,
message: "活动槽位 JAR 链接异常:" + detail,
})
}
return findings, nil
}
// unitRunning 判断 systemd 单元是否处于运行状态。
func unitRunning(unit systemd.Unit) bool {
return unit.ActiveState != "inactive" && unit.ActiveState != "failed"
}
// jarLinkStatus 检查槽位 JAR 路径是否为指向现存文件的符号链接。
// 返回 ok 与人类可读的详情;JAR 缺失、不是符号链接或目标不存在时返回 ok=false。
func jarLinkStatus(path string) (bool, string) {
info, err := os.Lstat(path)
if errors.Is(err, os.ErrNotExist) {
return false, "路径不存在"
}
if err != nil {
return false, "检查失败:" + err.Error()
}
if info.Mode()&os.ModeSymlink == 0 {
return false, "不是符号链接"
}
target, err := os.Readlink(path)
if err != nil {
return false, "读取链接目标失败:" + err.Error()
}
if _, err := os.Stat(path); err != nil {
return false, "链接目标不存在:" + target
}
return true, "指向 " + target
}