14 KiB
MODIFIED Requirements
Requirement: Состав и настройки агента
Агент SHALL состоять из файла agent.py (Python ≥ 3.9, только стандартная библиотека), запускателя run.py (те же
ограничения) и файла agent.json: hub_url, hub_token, jira.base_url, jira.auth (basic с user/password
или bearer с token), verify_ssl, ca_bundle, timeout, poll_seconds, необязательный auto_update (по
умолчанию true). hub_url вида http://… или https://… SHALL означать HTTP-транспорт, вида
folder:<абсолютный путь> — транспорт через общую папку. Всё остальное (id полей, типы, карта переходов, типы связей,
доска, темп) агент SHALL получать от хаба при старте и при изменении settings_version. Учётные данные Jira MUST NOT
отправляться в хаб и MUST NOT попадать в журнал, логи и общую папку. Команды agent.py: run (по умолчанию), check,
selftest, prune.
Scenario: Проверка без записи
- WHEN
python3 agent.py check - THEN агент печатает доступность хаба (версия настроек) и Jira (
GET /myself), в Jira ничего не пишет
Scenario: Проверка через папку
- WHEN
hub_url = "folder:/Volumes/My Shared Files/RSHB/jira-bridge"и реле хаба работает - THEN
checkпечатает «хаб: доступен через папку /Volumes/My Shared Files/RSHB/jira-bridge, настройки v, проект »; если каталога нет или реле не ответило за 10 с — строка с «НЕДОСТУПЕН» и причиной, код выхода 1
Scenario: Самопроверка новой версии
- WHEN
python3 agent.py selftest, хаб и Jira доступны - THEN печатается «jira-agent v» и строки
check; код 0; блокировка журнала не берётся, в Jira ничего не пишется; если хаб или Jira недоступны — код 3
Requirement: Доставка и запуск
README агента SHALL описывать: первую установку (копирование agent.py, run.py и agent.example.json через общую
папку в локальный каталог виртуалки, заполнение agent.json с выбором транспорта в hub_url, check), запуск
python3 run.py в терминале и через nohup с логом, опциональный launchd-plist с run.py, самообновление и
выключатель auto_update, ручное обновление run.py, prune (только при остановленном агенте, иначе откажет).
Версия агента SHALL быть в константе AGENT_VERSION файла (5 — самообновление) и в каждом claim и отчёте; хаб
предупреждает в agent_status, если версия ниже минимальной из настроек.
Scenario: Устаревший агент
- WHEN агент версии 1 выходит на связь, а в настройках
min_agent_version = 2 - THEN
agent_status.warning= «обновите агента», команды выдаются по-прежнему
ADDED Requirements
Requirement: Самообновление агента
Агент SHALL передавать в каждом claim update_state (содержимое update.json рядом с agent.py или null) и
launcher_version (из переменной окружения JIRA_AGENT_LAUNCHER или null). Проверять обновление агент SHALL только
после ответа claim с command = null, без сигнала остановки, при auto_update ≠ false и
agent_release.version > AGENT_VERSION. Версию с теми же to_version и sha256, что в update.json в состоянии
rejected или rolled_back, агент MUST NOT ставить снова. Без JIRA_AGENT_LAUNCHER агент MUST NOT выходить для
обновления: он пишет в лог «доступна v; для самообновления запускайте через run.py» не чаще раза в час.
Подготовка версии SHALL идти по шагам: GET /agent/release; совпадение version/sha256 ответа с agent_release
(иначе временный сбой); sha256 от content в UTF-8 равен sha256 (иначе rejected); content компилируется и
содержит AGENT_VERSION = <version> (иначе rejected); запись agent.py.new атомарно; python3 agent.py.new selftest
с таймаутом 120 с — код 0 годен, код 3 или таймаут — временный сбой, иной код — rejected с последними 500 символами
вывода. Годную версию агент SHALL отметить в update.json состоянием staged и завершить run с кодом 75. При
rejected и временном сбое agent.py.new SHALL удаляться, агент продолжает работу; при rejected пишется
update.json; повтор после временного сбоя — не раньше чем через 5 минут. Первый успешный claim версии, для которой
update.json = trial с to_version = AGENT_VERSION, SHALL записывать состояние ok. update.json пишется атомарно
(временный файл и переименование).
Scenario: Обновление в паузе
- WHEN агент v5 под запускателем получил
claimбез команды иagent_release = {version: 6, sha256: S}, файл проходит проверки иselftest - THEN рядом лежит
agent.py.new,update.json={state: staged, from_version: 5, to_version: 6, sha256: S}, процесс завершается с кодом 75; журнал не изменился
Scenario: Команда в очереди
- WHEN опубликована v6, а
claimагента v5 вернул команду - THEN агент выполняет команду и отчитывается; к обновлению переходит только после
claimбез команды
Scenario: Неверный sha256
- WHEN
GET /agent/releaseвернулcontent, sha256 которого не равенsha256 - THEN
agent.py.newне остаётся,update.json=rejected«sha256 не сходится»; следующийclaimнесёт этотupdate_state; агент v5 работает дальше и v6 с тем же sha256 больше не скачивает
Scenario: Хаб недоступен во время selftest
- WHEN
selftestновой версии вернул код 3 - THEN
agent.py.newудалён,update.jsonне изменён, следующая попытка — не раньше чем через 5 минут
Scenario: Запуск без запускателя
- WHEN агент v5 запущен
python3 agent.py, опубликована v6 - THEN агент не скачивает файл и не выходит; в логе раз в час «доступна v6; для самообновления запускайте через
run.py»;
claimнесётlauncher_version = null
Scenario: Подтверждение новой версии
- WHEN запускатель запустил v6,
update.json=trialсto_version = 6, первыйclaimv6 успешен - THEN
update.json=ok; лог «обновление до v6 подтверждено»
Requirement: Запускатель run.py
python3 run.py [аргументы] SHALL работать с файлами в каталоге самого run.py (agent.py, agent.py.new,
agent.py.prev, update.json) и запускать дочерним процессом agent.py run с переданными аргументами, переменной
окружения JIRA_AGENT_LAUNCHER = LAUNCHER_VERSION и унаследованными stdout/stderr. Запускатель SHALL брать
неблокирующую блокировку run.py.lock; занята — «запускатель уже работает», код 1. SIGINT при живом дочернем процессе
запускатель SHALL только запоминать как остановку человеком (Ctrl-C получает и агент), без дочернего процесса (пауза
перед перезапуском) — завершаться; SIGTERM — пересылать дочернему процессу. После сигнала остановки запускатель MUST NOT
запускать агента снова.
По коду выхода дочернего процесса:
0,130или завершение по SIGINT после Ctrl-C (или после SIGTERM запускателю) — запускатель завершается с тем же кодом; те же коды без сигнала запускателю — падение;75— приupdate.json=stagedи совпадении sha256agent.py.new: копияagent.pyвagent.py.prev, атомарная заменаagent.pyнаagent.py.new,update.json=trial, запуск новой версии; иначеagent.py.newудаляется,update.json=rejected«файл изменился после проверки», запуск текущей версии; если перед этим был Ctrl-C — после подмены запускатель завершается, пробный период начнётся при следующем старте;- иной код при
update.json=trial— откат; иначе — перезапуск той же версии с паузой 5 с, удваиваемой до 300 с при каждом следующем падении и сбрасываемой после 10 минут работы.
Пока update.json = trial, запускатель SHALL ждать состояния ok не дольше 10 минут; по истечении — SIGTERM, через
60 с SIGKILL и откат. Откат: agent.py.prev возвращается на место agent.py, update.json = rolled_back с причиной
(«код через с» или «не вышла на связь за 10 мин»), прежняя версия запускается без паузы. Файл agent.py MUST
существовать на каждом шаге подмены. При старте запускатель SHALL выполнять подмену, если update.json = staged и agent.py.new есть, и удалять
agent.py.new без состояния staged.
Scenario: Подмена версии
- WHEN дочерний v5 вышел с кодом 75,
update.json=stagedдля v6, sha256agent.py.newсовпадает - THEN
agent.py.prev— прежний v5,agent.py— v6,update.json=trial, запущен v6
Scenario: Откат при падении на старте
- WHEN
update.json=trialдля v7, дочерний процесс вышел с кодом 1 через 3 с - THEN
agent.py— снова v6 (изagent.py.prev),update.json={state: rolled_back, to_version: 7, error: "код 1 через 3 с"}, запущен v6; v6 видитrolled_backдля v7 с тем же sha256 и v7 больше не ставит
Scenario: Новая версия не выходит на связь
- WHEN
update.json=trial, дочерний процесс работает, но за 10 минутokне записан - THEN запускатель посылает SIGTERM, через 60 с при необходимости SIGKILL, делает откат с причиной «не вышла на связь за 10 мин»
Scenario: Новая версия сама вышла с кодом 0
- WHEN
update.json=trialдля v7, дочерний процесс вышел с кодом 0, сигналов запускатель не получал - THEN откат на v6 с причиной «код 0 через
с», как при падении
Scenario: Падение после подтверждения
- WHEN
update.json=ok, агент дважды подряд упал с кодом 1, проработав меньше минуты - THEN запускатель перезапускает ту же версию через 5 с, затем через 10 с; отката нет
Scenario: Остановка в терминале
- WHEN
python3 run.pyработает в терминале, нажат Ctrl-C - THEN агент доделывает команду и выходит с кодом 0, запускатель завершается с кодом 0; повторный Ctrl-C — агент выходит с 130, запускатель тоже
Scenario: Второй запускатель
- WHEN
run.pyработает, и запускается ещё одинrun.pyв том же каталоге - THEN второй пишет «запускатель уже работает» и завершается с кодом 1, агента не запускает