generated from mononoki/pyproj
123 lines
8.3 KiB
Markdown
123 lines
8.3 KiB
Markdown
# SIRMENT/core
|
||
|
||
SIRMENT — сервер системы обмена данными Artex. Узлами могут быть роботы,
|
||
операторские приложения и сервисы. Сервер управляет соединениями и сессиями
|
||
и передаёт данные по заданным маршрутам.
|
||
|
||
## Реализовано
|
||
|
||
- **Transport/TCP** принимает соединения и читает JSON-пакеты, разделённые LF.
|
||
`TCP.run()` запускает настоящий asyncio-сервер; `src/main.py` собирает Router
|
||
и SessionManager и запускает TCP на `0.0.0.0:9000`.
|
||
- **Session** хранит состояние подключения, версию протокола, метаданные и
|
||
последние данные по командам. Handshake проходит последовательность
|
||
`connection → token_ask → token_answer → handshake_ask → handshake_answer`.
|
||
После успешного ответа сессия переходит в `air`.
|
||
- **Heartbeat**: клиент отправляет `heartbeat_ask` с `payload`, сервер
|
||
отвечает `heartbeat_answer` и обновляет дедлайн.
|
||
- **Watcher**: у каждой сессии есть собственная asyncio-задача, проверяющая
|
||
дедлайн раз в секунду и передающая менеджеру событие `disconnect`.
|
||
- **Отключение** закрывает транспорт и переводит сессию в `stop`. Менеджер
|
||
сохраняет запись на время восстановления, затем удаляет её и маршруты.
|
||
- **Реконект** создаёт новый UID и собственный handshake. В `token_answer`
|
||
можно передать старые `token` и `upf`. Сессия в `stop` с совпадающим `upf`
|
||
отдаёт динамические метаданные и последние данные; Router переносит
|
||
исходящие маршруты и все входящие ссылки на новый UID. Старая сессия
|
||
получает состояние `migrated`, поэтому перенос нельзя повторить.
|
||
- **Router** передаёт полезную нагрузку отправителя каждому получателю.
|
||
`Session.send()` запускает отправку фоновой задачей; маршрутизатор не ждёт
|
||
завершения сетевой отправки.
|
||
|
||
Текущие сроки:
|
||
|
||
| Условие | Срок |
|
||
| --- | --- |
|
||
| От создания сессии до первого heartbeat, включая handshake | 60 секунд |
|
||
| После каждого heartbeat | 5 секунд |
|
||
| Сохранение отключённой сессии для восстановления | 60 секунд |
|
||
|
||
Проверка watcher выполняется раз в секунду: фактическое обнаружение таймаута
|
||
может произойти позже дедлайна на период проверки. Сам event loop продолжает
|
||
обслуживать остальные соединения во время `asyncio.sleep()`.
|
||
|
||
`in`/`out` описывают типы портов будущего редактора узлов. `active_in` и
|
||
`active_out` хранят динамическое состояние. Эти поля сами по себе не создают
|
||
маршруты. Значение `com` и общее правило объединения метаданных при реконекте
|
||
ещё не зафиксированы.
|
||
|
||
`connection.payload.token` — токен робота, сохраняемый в метаданных `auth`;
|
||
его будущая проверка по whitelist роутера пока не реализована. UID/upf в
|
||
`token_answer` служат отдельно для восстановления сессии.
|
||
|
||
По согласованному протоколу отказ handshake означает только буквальный JSON
|
||
`false`; остальные значения `status` означают успех. Текущий `if not status`
|
||
ещё использует ложность Python и расходится с этим правилом.
|
||
Payload данных сохраняется и пересылается как JSON-значение без автокаста
|
||
в словарь. Ошибка разбора входного пакета может завершить обработчик клиента;
|
||
TCP-finally закрывает его сессию, остальные соединения продолжают обслуживаться.
|
||
|
||
## Незавершённое и ограничения
|
||
|
||
- GUI/API редактирования маршрутов; автоматическое создание маршрутов и
|
||
правила подписок.
|
||
- Авторизация. Совпадение UID/upf сейчас используется для восстановления и
|
||
не заменяет полноценную авторизацию.
|
||
- Полноценное переключение диалектов: используется `v1`; `v2` и `short`
|
||
содержат только черновые имена полей и не имеют полного набора команд.
|
||
- WebSocket/Socket.IO (`sio`).
|
||
- Сохранение сессий, данных и маршрутов после перезапуска процесса.
|
||
- Отдельный ответ клиенту с результатом восстановления. Heartbeat подтверждает
|
||
новый handshake, но не доказывает успешный перенос старой сессии.
|
||
- Настройка адреса и порта через CLI/окружение: сейчас они заданы в `main.py`.
|
||
|
||
Актуальные результаты, ошибки реализации и ограничения проверки приведены
|
||
в [отчёте тестов](tests/REPORT.md). Изменения исходников сервера выполняет автор
|
||
проекта; доработка тестов и документации не редактирует `src`.
|
||
|
||
## Запуск
|
||
|
||
Версия Python указана в `.py-version` — Python 3.11. Из корня репозитория:
|
||
|
||
```sh
|
||
python -m pip install -r req.txt
|
||
python src/main.py
|
||
```
|
||
|
||
Общий клиент находится в отслеживаемом `examples/client.py`:
|
||
|
||
```sh
|
||
python examples/client.py rover --count 10 --interval 1
|
||
python examples/client.py rover --old-token 123 --old-upf 456
|
||
```
|
||
|
||
На Windows можно использовать `src/start_server.bat` и
|
||
`examples/start_client.bat`; они берут Python из
|
||
`D:\_Programs\anaconda\envs\sites`.
|
||
|
||
## Тесты
|
||
|
||
```sh
|
||
python -m pip install -r req-dev.txt
|
||
python -m pytest -q --cov=src --cov-branch --cov-report=term-missing --cov-report=xml
|
||
```
|
||
|
||
Тесты используют общий клиент из `examples/`, поэтому не зависят от локальной
|
||
игнорируемой папки `src/.test`. Интеграционные сценарии работают с настоящим
|
||
TCP-сервером. Отдельно выполняется настоящий `src/main.py` в дочернем процессе.
|
||
Подробности и команды запуска — в [tests/README.md](tests/README.md).
|
||
Новые регрессии остаются обычными падающими тестами до исправления реализации.
|
||
|
||
## Docker и CI
|
||
|
||
Тесты выполняются для Pull Requests в `main` и при ручном запуске workflow.
|
||
Docker-образы собираются при push в `deploy` и создании тегов `v*.*.*`.
|
||
Образ запускает `python src/main.py`.
|
||
|
||
## Структура
|
||
|
||
```text
|
||
src/ TCP, сессии, маршрутизатор и точка входа
|
||
examples/ Общий клиент и батник ручного запуска
|
||
tests/ Модульные, TCP-интеграционные и процессные тесты
|
||
```
|