138 lines
4.3 KiB
Go
138 lines
4.3 KiB
Go
|
|
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
|
|||
|
|
}
|