Связь с НРД: проверка M2M не повторяется при каждом перезапуске bj; молчание НРД на справочник — жёлтый, шапку не красит
22.10.2026 на 9 за день ушло больше 100 запросов #M2MHR (проверка + каждый перезапуск), с 14:01 НРД перестал отвечать — шапка уходила в красный при работающем обмене. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
@@ -39,6 +39,44 @@ const (
|
||||
|
||||
var nsdHistoryFile = "/var/lib/bj/.bj/nsd-health.json"
|
||||
|
||||
// nsdProbeFile — время последней проверки M2M и ответа на неё: переживает перезапуск bj. Без него каждый
|
||||
// перезапуск (установка обновления) слал запрос справочника через минуту после старта; 22.10.2026 на 9
|
||||
// набралось больше 100 запросов за день, и с 14:01 НРД перестал на них отвечать.
|
||||
var nsdProbeFile = "/var/lib/bj/.bj/nsd-probe.json"
|
||||
|
||||
type nsdProbeSaved struct {
|
||||
Sent time.Time `json:"sent"`
|
||||
Reply time.Time `json:"reply"`
|
||||
}
|
||||
|
||||
func nsdProbeSave() {
|
||||
nsdH.Lock()
|
||||
v := nsdProbeSaved{Sent: nsdH.ProbeSentAt, Reply: nsdH.ProbeReplyAt}
|
||||
nsdH.Unlock()
|
||||
if raw, err := json.Marshal(v); err == nil {
|
||||
_ = os.WriteFile(nsdProbeFile, raw, 0o600)
|
||||
}
|
||||
}
|
||||
|
||||
// nsdProbeLoad — восстановить время проверки после перезапуска; возвращает, когда слать следующую.
|
||||
func nsdProbeLoad(now time.Time) time.Duration {
|
||||
var v nsdProbeSaved
|
||||
if raw, err := os.ReadFile(nsdProbeFile); err == nil && json.Unmarshal(raw, &v) == nil {
|
||||
nsdH.Lock()
|
||||
if nsdH.ProbeSentAt.IsZero() {
|
||||
nsdH.ProbeSentAt = v.Sent
|
||||
}
|
||||
if v.Reply.After(nsdH.ProbeReplyAt) {
|
||||
nsdH.ProbeReplyAt = v.Reply
|
||||
}
|
||||
nsdH.Unlock()
|
||||
if left := nsdProbeEvery - now.Sub(v.Sent); left > time.Minute {
|
||||
return left
|
||||
}
|
||||
}
|
||||
return time.Minute
|
||||
}
|
||||
|
||||
// Состояния участков по возрастанию тяжести.
|
||||
const (
|
||||
segOK = "ok"
|
||||
@@ -105,11 +143,12 @@ type nsdSample struct {
|
||||
// nsdProbeReply — пришёл справочник: НРД ответил (по нашей проверке или сам).
|
||||
func nsdProbeReply(at time.Time) {
|
||||
nsdH.Lock()
|
||||
defer nsdH.Unlock()
|
||||
if !nsdH.ProbeSentAt.IsZero() && nsdH.ProbeReplyAt.Before(nsdH.ProbeSentAt) && at.After(nsdH.ProbeSentAt) {
|
||||
nsdH.ProbeLatency = at.Sub(nsdH.ProbeSentAt)
|
||||
}
|
||||
nsdH.ProbeReplyAt = at
|
||||
nsdH.Unlock()
|
||||
nsdProbeSave()
|
||||
}
|
||||
|
||||
// nsdDelivery — итог передачи пакета в НРД (из клиента ИШ).
|
||||
@@ -200,7 +239,12 @@ func nsdHealthNow(rc *RuntimeConfig, now time.Time) NSDHealth {
|
||||
case nsdH.ProbeErr != "":
|
||||
m.State, m.Text, m.Detail = segErr, "запрос проверки не доставлен", nsdH.ProbeErr
|
||||
case pending && now.Sub(nsdH.ProbeSentAt) >= nsdProbeErrAfter:
|
||||
m.State, m.Text = segErr, "НРД не ответил на проверку "+humanDur(now.Sub(nsdH.ProbeSentAt))
|
||||
// запрос доставлен, но ответа нет: НРД отвечает на запрос справочника не всегда (ограничивает их
|
||||
// число) — это не обрыв связи, поэтому жёлтый; обрыв видно по участкам «Шлюз» и «НРД»
|
||||
m.State, m.Text = segWarn, "НРД не ответил на проверку "+humanDur(now.Sub(nsdH.ProbeSentAt))
|
||||
if !nsdH.ProbeReplyAt.IsZero() {
|
||||
m.Text += " (прошлый ответ " + agoText(nsdH.ProbeReplyAt, now) + ")"
|
||||
}
|
||||
case pending && now.Sub(nsdH.ProbeSentAt) >= nsdProbeWarnAfter:
|
||||
m.State, m.Text = segWarn, "ждём ответ НРД на проверку "+humanDur(now.Sub(nsdH.ProbeSentAt))
|
||||
case pending && nsdH.ProbeReplyAt.IsZero():
|
||||
@@ -239,6 +283,9 @@ func nsdHealthNow(rc *RuntimeConfig, now time.Time) NSDHealth {
|
||||
// серым работающий обмен. Серый — только когда по известным участкам связь не подтверждена.
|
||||
h.State = segOK
|
||||
for _, s := range h.Segs {
|
||||
if s.Key == "m2m" && ns.State == segOK && s.State != segErr {
|
||||
continue // НРД отвечает шлюзу — задержка ответа на справочник не красит шапку (видна в панели)
|
||||
}
|
||||
if s.State != segUnknown && segRank(s.State) > segRank(h.State) {
|
||||
h.State = s.State
|
||||
}
|
||||
@@ -284,6 +331,7 @@ func (s *Server) sendNSDProbe(ctx context.Context) {
|
||||
nsdH.Lock()
|
||||
nsdH.ProbeSentAt, nsdH.ProbeErr = time.Now(), ""
|
||||
nsdH.Unlock()
|
||||
nsdProbeSave()
|
||||
cctx, cancel := context.WithTimeout(ctx, 40*time.Second)
|
||||
defer cancel()
|
||||
if _, err := sendHandbookRequest(cctx, s.igwClient, s.igwChannel); err != nil {
|
||||
@@ -298,7 +346,7 @@ func (s *Server) sendNSDProbe(ctx context.Context) {
|
||||
func (s *Server) nsdHealthLoop(ctx context.Context) {
|
||||
healthRegister("nsd-health", 3*time.Minute)
|
||||
nsdHistoryLoad()
|
||||
probeT := time.NewTimer(time.Minute)
|
||||
probeT := time.NewTimer(nsdProbeLoad(time.Now())) // после перезапуска — не раньше часа с прошлой проверки
|
||||
defer probeT.Stop()
|
||||
minute := time.NewTicker(time.Minute)
|
||||
defer minute.Stop()
|
||||
|
||||
@@ -32,6 +32,7 @@ func nsdTestReset(t *testing.T) {
|
||||
sendFails.loaded = false
|
||||
oldHist := nsdHistoryFile
|
||||
nsdHistoryFile = filepath.Join(t.TempDir(), "h.json")
|
||||
nsdProbeFile = filepath.Join(t.TempDir(), "p.json")
|
||||
t.Cleanup(func() {
|
||||
sendFailFile, sendFails.loaded, nsdHistoryFile = old, false, oldHist
|
||||
igwWatch.mu.Lock()
|
||||
@@ -98,7 +99,8 @@ func TestNSDHealthStates(t *testing.T) {
|
||||
nsdH.ProbeSentAt = now.Add(-12 * time.Minute)
|
||||
nsdH.ProbeReplyAt = now.Add(-25 * time.Minute)
|
||||
nsdH.Unlock()
|
||||
if h = nsdHealthNow(rc, now); segState(h, "m2m") != segErr {
|
||||
// молчание на запрос справочника при живом НРД — жёлтый участок, шапку не красит
|
||||
if h = nsdHealthNow(rc, now); segState(h, "m2m") != segWarn || h.State != segOK {
|
||||
t.Fatalf("НРД молчит на проверку 12 мин: %+v", h)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user