Профілі були даними без виконавця. Тепер ланцюг замкнено: черга в БД → диспетчер у AgentService → зонд по SSH/Telnet → вивантаження стрімом → звірка sha256 і дедуплікація. Агент (internal/ncmx): - усе побудовано навколо пошуку промпту: у консолі немає ані коду завершення, ані довжини відповіді - промпт шукається лише в хвості 512 байтів, інакше "banner motd #" обривав би збір на середині конфігу - дедлайн на паузу між байтами, а не на всю операцію: збір із шасі триває хвилини, а тиша означає завислий пристрій або хибний regex - ключі SSH обладнання не звіряються свідомо: залізо міняє їх з кожною прошивкою, і known_hosts на сотні пристроїв означав би не збирати конфіги зовсім Сервер: черга ncm.jobs із FOR UPDATE SKIP LOCKED (два екземпляри не надішлють одне завдання двічі), диспетчер, прибирання завислих. Знайдено живим прогоном: - сервер ігнорував поле encoding: агент стискав gzip і рахував sha256 від оригіналу, сервер рахував від стиснених байтів і відхиляв кожен бекап. Поле в контракті з Етапу 2, реалізації не було — інтеграційний тест користувався "none" і повз дірку проходив - промпт обрізався не по рядку: шаблон [>#]\s*$ ловить лише символ, і в конфізі лишалось ім'я пристрою окремим рядком - у конфіг потрапляли escape-послідовності bash і подвоєні \r\r\n від PTY: 295 байтів / 12 рядків замість 286 / 10. Після виправлення — 285 / 10, різниця лише у фінальному переводі рядка Живий прогін проти стенду: success, конфіг 285 байтів, повторний збір — unchanged без другої версії. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
86 lines
2.9 KiB
Go
86 lines
2.9 KiB
Go
package grpcapi
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
|
||
npv1 "github.com/netpulse/netpulse/gen/go/netpulse/v1"
|
||
)
|
||
|
||
// DispatchConfigJobs роздає завдання збору конфігу живим сесіям.
|
||
//
|
||
// Опитування таблиці, а не сповіщення: чергу наповнює інший процес
|
||
// (REST), і LISTEN/NOTIFY тут дав би доставку «здебільшого» — воно не
|
||
// переживає перезапуск слухача. Ціна опитування — один дешевий запит за
|
||
// індексом раз на кілька секунд, і лише тоді, коли хоч один зонд
|
||
// на зв'язку.
|
||
func (s *Service) DispatchConfigJobs(ctx context.Context, every time.Duration) {
|
||
if every <= 0 {
|
||
every = 5 * time.Second
|
||
}
|
||
t := time.NewTicker(every)
|
||
defer t.Stop()
|
||
|
||
// Зависле завдання повертається у відмову за десять хвилин: зонд міг
|
||
// зникнути разом із ним, і без цього хост лишився б без бекапів
|
||
// назавжди.
|
||
reap := time.NewTicker(2 * time.Minute)
|
||
defer reap.Stop()
|
||
|
||
s.log.Info("диспетчер збору конфігів запущено", "інтервал", every)
|
||
|
||
for {
|
||
select {
|
||
case <-ctx.Done():
|
||
return
|
||
|
||
case <-reap.C:
|
||
if n, err := s.store.ReapStuckJobs(ctx, 10*time.Minute); err != nil {
|
||
s.log.Warn("прибирання завислих завдань", "err", err)
|
||
} else if n > 0 {
|
||
s.log.Warn("завдання збору зависли й позначені як невдалі", "рядків", n)
|
||
}
|
||
|
||
case <-t.C:
|
||
online := s.onlineAgentIDs()
|
||
if len(online) == 0 {
|
||
continue
|
||
}
|
||
|
||
jobs, err := s.store.ClaimConfigJobs(ctx, online, 16, s.ring)
|
||
if err != nil {
|
||
s.log.Error("вибірка завдань збору", "err", err)
|
||
continue
|
||
}
|
||
|
||
for _, j := range jobs {
|
||
ok := s.PushToAgent(j.AgentID, &npv1.ControlDown{
|
||
Payload: &npv1.ControlDown_ConfigJob{ConfigJob: j.Job},
|
||
})
|
||
if !ok {
|
||
// Сесія обірвалась між вибіркою й відправкою.
|
||
// Повертаємо в чергу, а не втрачаємо: наступний тік
|
||
// віддасть завдання, щойно зонд повернеться.
|
||
_ = s.store.FinishConfigJob(ctx, j.JobID, "failed",
|
||
"зонд відключився до надсилання завдання", "")
|
||
continue
|
||
}
|
||
s.log.Info("завдання збору надіслано",
|
||
"job_id", j.JobID, "agent", j.AgentID,
|
||
"device", j.Job.GetDevice().GetName())
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// onlineAgentIDs — хто зараз на зв'язку.
|
||
func (s *Service) onlineAgentIDs() []string {
|
||
s.mu.RLock()
|
||
defer s.mu.RUnlock()
|
||
|
||
out := make([]string, 0, len(s.sessions))
|
||
for id := range s.sessions {
|
||
out = append(out, id)
|
||
}
|
||
return out
|
||
}
|