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
|
||
}
|