Состояния колонки «Upgrade ROS» и осознанный откат ROS до версии канала
После смены канала на long-term колонка показывала «актуально», хотя
версия канала (7.23.7) старше установленной (7.24.4) — docs/changes/023:
- состояния колонки: «↑ X», «актуально», «канал: X» (версия канала старше
установленной), «проверка не удалась» (ошибка check-for-updates больше
не маскируется под «актуально»), «—»;
- откат ROS до версии канала как отдельная задача ros_downgrade: проверка
версии на устройстве → обязательный бэкап (сбой прерывает) → install;
- UI: «Откатить ROS…» в меню устройства и групповой пункт в «Обновление»,
окно с вводом целевой версии, список не затрагиваемых устройств;
- API: POST /devices/{id}/update/downgrade и /batch/ros_downgrade с
обязательным target_version. Штатное обновление откат не выполняет.
Тесты: 33 из 33. Стенд: 422 без версии, задачи отката на недоступном
устройстве завершаются на проверке без бэкапа. Реальный откат и ручная
проверка UI пользователем на момент коммита не выполнены.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0bda0038d0
commit
ae826fc562
14 files changed
+437
-13
No files matched your search
@@ -0,0 +1,95 @@
|
||||
# План: 023 — состояния колонки «Upgrade ROS» и осознанный откат ROS до версии канала
|
||||
|
||||
## Context
|
||||
|
||||
Пользователь переключил канал обновлений на `long-term` (и `testing`), а колонка «Upgrade ROS» продолжала показывать «актуально».
|
||||
Разбор (оркестратор):
|
||||
- Цепочка обновления статуса корректна: `ops.set_channel` → `refresh_status` → `check-for-updates` (в REST синхронный — ответ приходит
|
||||
с итоговой `latest-version`, проверено на реальном устройстве) → статус сохраняется.
|
||||
- Версии каналов MikroTik сейчас: stable 7.24.4, **testing 7.24.4**, **long-term 7.23.7**, development 7.25beta5; у устройств установлена 7.24.4.
|
||||
На `testing` «актуально» верно. На `long-term` версия канала **старше** установленной: RouterOS пишет `New version is available`
|
||||
(установка = откат), а приложение по строгому сравнению (`ros.version_newer`, коммит `0914209`) показывает «актуально» — вводит в заблуждение.
|
||||
- Попутный дефект: если последний `check-for-updates` завершился ошибкой (`ros_check_error`), а старая `latest-version` сохранилась,
|
||||
таблица тоже показывает «актуально»; ошибка видна только при пустой `latest-version`.
|
||||
|
||||
Требование пользователя: переход stable → long-term должен быть возможен как **осознанная опция отката (downgrade)**.
|
||||
|
||||
Решения пользователя:
|
||||
- Подтверждение — **окно с вводом целевой версии**: кнопка активна только после ввода версии; сервер проверяет, что она совпадает с текущей версией канала.
|
||||
- **Обязательный бэкап** перед откатом; бэкап не удался — откат не выполняется.
|
||||
- Откат для **одного устройства и группой** (UI и API).
|
||||
- Реальный откат на устройстве запускает **пользователь** в UI; оркестратор проверяет всё без реального отката (тесты с имитацией RouterOS).
|
||||
|
||||
Техническая основа: в RouterOS 7 `/system/package/update/install` при версии канала старше установленной скачивает версию канала и выполняет откат
|
||||
(отсюда `New version is available`). Штатное «Upgrade ROS» (`ros.install_ros_update`) откат по-прежнему **не** выполняет.
|
||||
|
||||
## Изменения
|
||||
|
||||
### Отображение (`app/ui/templates/_devices.html`, `app/ros/operations.py` или `app/services/devices.py`)
|
||||
- Хелпер состояния (рядом с `devices.has_ros_update`): `ros_state(st) -> "unknown" | "check_error" | "update" | "current" | "downgrade"`:
|
||||
`unknown` — нет `ros_latest`/`ros_installed`; `check_error` — есть `ros_check_error`; `update` — `version_newer(latest, installed)`;
|
||||
`downgrade` — `version_newer(installed, latest)`; иначе `current`. Экспортировать в шаблоны (как `ros_newer`).
|
||||
- Колонка «Upgrade ROS»: `update` — как сейчас («↑ X», warn); `current` — «актуально»; **`downgrade`** — нейтральная метка «канал: X»
|
||||
с подсказкой «Версия канала X старше установленной Y. Обновление не требуется; откат — пункт «Откатить ROS…»»;
|
||||
**`check_error`** — метка-предупреждение «проверка не удалась», текст ошибки в подсказке; `unknown` — «—» как сейчас.
|
||||
- Стили меток — существующие классы `badge` и токены; новых цветов нет.
|
||||
- Фильтр «Обновления» не меняется (`downgrade` не считается обновлением).
|
||||
|
||||
### Операция отката (`app/ros/operations.py`, `app/services/ops.py`, `app/services/jobs.py`)
|
||||
- `ros.downgrade_ros(c, target_version) -> str`: `check-for-updates` (timeout как в `install_ros_update`), чтение `system/package/update`;
|
||||
если `latest-version != target_version` → `RosError("Версия канала X не совпадает с подтверждённой Y — откат отменён")`;
|
||||
если не `version_newer(installed, latest)` → `RosError("Версия канала X не старше установленной Y — это не откат")`;
|
||||
иначе `POST system/package/update/install` (обрыв соединения при перезагрузке — штатно, как в `install_ros_update`);
|
||||
сообщение «Откат Y → X запущен, устройство перезагрузится». **Переиспользовать** разбор ответа и обработку обрыва из `install_ros_update`
|
||||
(вынести общую часть, не копировать).
|
||||
- Проверка до бэкапа: вынести первую половину (check + сверка версий) в `ros.check_downgrade(c, target) -> (installed, latest)`,
|
||||
чтобы не делать бэкап устройства, которое откатывать нельзя.
|
||||
- `ops.run_ros_downgrade(device_id, target_version) -> str`: 1) подключение, `check_downgrade`; 2) **`await run_backup(device_id)`** — ошибка
|
||||
прерывает задачу, откат не запускается (бэкап привязывается к той же задаче через ContextVar `job_id`); 3) новое подключение,
|
||||
повторная `check_downgrade` + `install` (`downgrade_ros`). Итоговое сообщение включает ключи бэкапа.
|
||||
- `jobs.JOB_TYPES["ros_downgrade"] = ops.run_ros_downgrade`; параметр `target_version` — через `params` (как `channel` у `set_channel`).
|
||||
Подпись типа в `_jobs.html`: «Откат ROS».
|
||||
|
||||
### API (`app/api/v1.py`)
|
||||
- `POST /api/v1/devices/{id}/update/downgrade` — тело `{"target_version": "7.23.7"}` (обязательное, непустое) → 202 `{"job_ids"}`.
|
||||
- `POST /api/v1/batch/ros_downgrade` — `BatchIn` + `target_version` → 202 `{"job_ids"}`. Существующий `/batch/{action}` не меняется
|
||||
(`ros_downgrade` в его `Literal` **не** добавлять — откат только через эндпоинт с обязательной версией).
|
||||
- Предварительная проверка по кэшу статуса не делается в API (достоверна только проверка на устройстве в задаче); задача на устройстве
|
||||
с несовпадающей версией завершается `failed` с понятным сообщением.
|
||||
|
||||
### UI (`app/ui/routes.py`, шаблоны, `app/ui/static/app.js` при необходимости)
|
||||
- Окно `GET /ui/dialog/downgrade?device_ids=…` (для одного устройства — из меню «⋯» строки, пункт «Откатить ROS…», виден только при
|
||||
состоянии `downgrade`; для группы — пункт «Откатить ROS до версии канала…» в меню «Обновление» с выбранными устройствами):
|
||||
- таблица выбранных устройств: имя, установлено → версия канала (из кэша статуса), канал;
|
||||
- устройства не в состоянии `downgrade` — отдельным списком «Не будут затронуты» (причина: «версия канала не старше установленной» / «нет данных проверки»);
|
||||
- если у устройств разные версии канала — откатываются только совпадающие с введённой, остальные показаны как «не будут затронуты»;
|
||||
- предупреждение: перед откатом создаётся бэкап; устройство перезагрузится; конфигурация новой версии может быть частично несовместима;
|
||||
- поле «Введите целевую версию» (`input[data-enables]` — существующий механизм `syncEnables`), кнопка «Откатить» (класс опасного действия, как «Удалить») неактивна, пока поле пусто.
|
||||
- `POST /ui/downgrade`: `device_ids`, `target_version`; сервер оставляет устройства, у которых в кэше `ros_state == "downgrade"` и
|
||||
`ros_latest == target_version`; если введённая версия не совпала ни с одним — окно остаётся открытым с ошибкой «Версия не совпадает
|
||||
с версией канала выбранных устройств»; иначе задачи `ros_downgrade` и обновление панели «Задачи» (как у групповых действий; окно закрывается — `HX-Refresh` или закрытие окна и обновление `#jobs`).
|
||||
- Разметка окна — по образцу существующих окон (`_events_clear.html` — окно подтверждения опасного действия, `_device_form.html`).
|
||||
|
||||
## Тесты (минимально, `tests/test_app.py`, RouterOS — `httpx.MockTransport`, как в существующих тестах)
|
||||
- `ros_state`: update / current / downgrade / check_error / unknown.
|
||||
- `run_ros_downgrade`: версия не совпала → задача `failed`, **бэкап и install не вызывались**; совпала → бэкап вызван **до** install (порядок),
|
||||
install вызван; бэкап упал → install не вызывался.
|
||||
- API: `target_version` обязателен (422 без него); `/batch/ros_downgrade` → 202 и тип задачи `ros_downgrade`; `/batch/ros_downgrade` через `/batch/{action}` недоступен (422).
|
||||
- UI: `POST /ui/downgrade` с несовпадающей версией → ошибка в окне, задач нет.
|
||||
|
||||
## Документация
|
||||
README: «Возможности»/«Интерфейс» (состояния колонки, откат), таблица API (два эндпоинта), типы задач, число тестов, строка 023 в истории изменений.
|
||||
`summary.md` — оркестратор.
|
||||
|
||||
## Исполнение
|
||||
Исполнитель (Sonnet): код, тесты, README, пересборка стенда. Тесты не запускает, не коммитит, `.env` не читает.
|
||||
**Реальный откат на устройствах не запускать** (ни исполнителю, ни оркестратору): его выполняет пользователь в UI.
|
||||
|
||||
## Проверка
|
||||
- `pytest` — все зелёные.
|
||||
- Стенд (override 8001, `--force-recreate`): новый код; колонка на реальных данных — устройства на `stable` с 7.24.4 «актуально»
|
||||
(при переключении пользователем на long-term — «канал: 7.23.7» и пункт «Откатить ROS…»).
|
||||
- Безопасные проверки на стенде: `POST /api/v1/devices/{id}/update/downgrade` для временного устройства `192.0.2.1` → 202, задача `failed`
|
||||
на проверке (устройство недоступно), бэкап не создан; `target_version` отсутствует → 422. Временное устройство удаляется.
|
||||
- Ручная проверка — пользователь: отображение состояний; окно отката (неактивная кнопка, неверная версия, список «не будут затронуты»);
|
||||
реальный откат на выбранном устройстве — с бэкапом в S3 до перезагрузки.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Итоги: 023 — состояния колонки «Upgrade ROS» и осознанный откат ROS до версии канала
|
||||
|
||||
## Разбор исходной жалобы
|
||||
После смены канала колонка показывала «актуально». Цепочка обновления статуса корректна (`check-for-updates` в REST синхронный — проверено
|
||||
на реальном устройстве). Причина — отображение: версии каналов MikroTik — stable 7.24.4, testing 7.24.4 (совпадает — «актуально» верно),
|
||||
long-term 7.23.7 (старше установленной 7.24.4 — RouterOS пишет `New version is available`, а приложение по строгому сравнению показывало «актуально»).
|
||||
Попутно: при ошибке `check-for-updates` и сохранённой старой `latest-version` тоже показывалось «актуально».
|
||||
|
||||
## Сделано
|
||||
- `devices.ros_state(st)`: `unknown` / `check_error` / `update` / `downgrade` / `current`. Колонка «Upgrade ROS»: «↑ X», «актуально»,
|
||||
нейтральная «канал: X» (версия канала старше установленной), предупреждение «проверка не удалась» (ошибка в подсказке), «—».
|
||||
Фильтр «Обновления» и штатное обновление не изменились.
|
||||
- `ros.check_downgrade` (версия канала = подтверждённой и старше установленной), `ros.downgrade_ros`; общая часть с `install_ros_update`
|
||||
вынесена (`_check_update`, `_install` — обрыв соединения при перезагрузке штатен).
|
||||
- Задача `ros_downgrade` (`ops.run_ros_downgrade`, параметр `target_version`): проверка → **обязательный бэкап** (сбой прерывает) →
|
||||
повторная проверка и install; сообщение содержит ключи бэкапа, бэкап привязан к той же задаче.
|
||||
- API: `POST /api/v1/devices/{id}/update/downgrade`, `POST /api/v1/batch/ros_downgrade` (обязательный `target_version`, 202);
|
||||
маршрут зарегистрирован до `/batch/{action}`, в общий `Literal` откат не добавлен.
|
||||
- UI: пункт «Откатить ROS…» в меню «⋯» (только в состоянии `downgrade`), «Откатить ROS до версии канала…» в меню «Обновление»;
|
||||
окно `_dialog_downgrade.html`: что будет откатано, «Не будут затронуты» с причинами, предупреждения, ввод целевой версии
|
||||
(кнопка неактивна до ввода), при несовпадении — ошибка в окне без задач. Подпись задачи «Откат ROS».
|
||||
|
||||
## Проверено
|
||||
- `pytest`: 33 из 33 (новые: состояния, порядок «бэкап → install» и отсутствие install при несовпадении/сбое бэкапа, API, окно UI).
|
||||
- Стенд (8001), временное недоступное устройство: без `target_version` → 422; оба эндпоинта → 202; задачи `ros_downgrade` → `failed`
|
||||
на проверке, **бэкап не создавался**. Временное устройство удалено.
|
||||
- Реальные устройства (все на stable 7.24.4): «актуально», пунктов отката нет — верно.
|
||||
- Боевые данные: группы, устройства, бэкапы совпадают по ID; добавлено 3 задачи и 12 событий проверки
|
||||
(одна — лишний `ros_update` на временном недоступном устройстве, ошибочно запущенный оркестратором; на данные не повлиял).
|
||||
|
||||
## Оговорки
|
||||
- **Реальный откат не выполнялся**: поведение RouterOS 7 `update/install` при версии канала старше установленной (скачать и откатить)
|
||||
подтверждено статусом устройства (`New version is available`), но не прогоном — проверяет пользователь.
|
||||
- Прошивка RouterBOARD после отката ROS остаётся новее — отдельно не обрабатывается.
|
||||
- Ручная проверка UI пользователем на момент коммита не подтверждена.
|
||||
Reference in new issue
Block a user