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 }