Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

831 rader
34 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

Вопросы
-------
1. >> Что является ресурсом?
* commands -- информация о командах, их список, группы, описания и тд.
* workers(?) -- воркеры, исполняющие команды. Сомнительно.
Возможно есть смысл скрыть реализацию, т.е. воркеров, под некоторыми
другими сущностями. Например, execution и configuration.
workers -- мб оставить как дополнительный низкоуровневый интерфейс.
<< Ресурсы: commands, configuration, execution, workers (low_level)
2. >> Что делать с демонами-воркерами, обнаруженными при инциализации сервера,
но не зарегистрированными в БД?
<< Убиваем их и удаляем PID-файлы.
3. >> Что делать с демонами-воркерами, для которых найдены PID-файлы, по PID
есть информация в БД, но WID из имени PID-файла не совпадает с WID в БД.
<< Убиваем воркера, удаляем PID-файл и запись в БД.
4. >> Стоит ли убрать разделение команд и их id?
<< Не стоит.
5. >> Нужна ли возможность сброса текущих параметров конфигурации?
<< Да.
Взаимодействие с воркером с точки зрения сервера
------------------------------------------------
1. Сервер получает от клиента запрос, POST /configs/{command_id}. Далее:
* Сервер создает объект воркера команды и запускает с его помощью
демона-рабочего, оставляя его в режиме ожидания конфигурационных данных.
* После создания объекта воркера в БД сохраняется основная информация
необходимая для воссоздания объекта воркера в новом процессе сервера. Такая
ситуация может возникнуть при переходе на новую версию сервера:
- int wid -- идентификатор воркера;
- str socket -- путь к сокету для чтения данных воркера;
- int daemon_pid -- PID демона-воркера для взаимодействия с ним.
* В ответ на запрос сервер получает список доступных ресурсов, включающих
в себя в том числе действия воркера, а также URI этих ресурсов и
доступные для этого URI http-методы. На данном этапе:
- configure (patch) -- изменить некоторые параметры конфигурации;
- cancel (delete) -- закончить конфигурацию без продолжения выполнения.
По сути удаляет данную конфигурацию как ресурс;
- execute (post) -- запустить выполнение команды с дефолтной
конфигурацией. По сути удаляет конфигурацию как ресурс и создает
новый ресурс -- выполнение.
2. Сервер получает от клиента запрос PATCH /configs/{WID}/parameters,
содержащий в теле конфигурационные данные. Далее:
* Формируется конфигурационное сообщение (1a), содержащее в себе список
объектов, описывающих один или несколько параметров команды, которое
затем отправляется воркеру.
* Oжидается ответ от воркера (1a) с информацией о результатах обработки
параметров, найденных ошибках и т.д, который пересылается клиенту.
* Конфигурационный цикл повторяется.
Возможно стоит добавить на стороне сервера таймаут при получении ответа от
воркера.
3. Сервер получает от клиента запрос POST /execs/{WID}, указывающий
закончить конфигурацию команды и начать выполнение.
Далее:
* Формируется сообщение воркеру (3a), сообщающее уже ему об окончании цикла
конфигурации.
* Если есть неисправленные ошибки, воркер отвечает на него сообщением (4b),
содержащим оповещение о том, что он заканчивает работу;
Если ошибок нет, то сообщением (4a), оповещающим об успешном запуске
скрипта команды.
* В случае, когда обработка параметров прошла с ошибками -- сразу после
выдачи сообщения об ошибках конфигурации, воркер отправляет сообщение
об окончании своей работы, содержащее список ошибок (9b) и (?) ждет
подтверждение от сервера (?).
4. Сервер получает от клиента запрос GET /execs/{WID}/output на
получение имеющихся выходных данных воркера. Далее:
* Cервер читает все, что есть на сокете воркера. Сообщения (5a, 5b или 6a)
на сокете разделены нулевым символом '\0'. Собирается список полных
сообщений на сокете, формируется сообщение, содержащее этот список,
которое отправляется клиенту.
5. Сервер получает от клиента запрос PATCH /execs/{WID}/input на передачу
воркеру ввода, который был запрошен последним в одном из сообщений ранее
переданных клиенту через сервер. Далее:
* Сервер формирует сообщение (7a) воркеру с данными ввода и отправляет его
воркеру. Если данных нет, что означает, что ввод был сброшен
пользователем, воркер отправляет серверу сообщение (7b) об некорректном
вводе, а метод input объекта ввода/вывода кидает в скрипте специальное
исключение.
Возможно, необходимо убеждаться в том, что данные успешно получены воркером.
Воркер должен ответить сообщением (8a) об успешном получении данных.
- Если данное сообщение не получено в течение некоторого времени
(таймаута) -- выполняем некоторые действия по диагностике и устранению
последствий ошибки, затем отправляем клиенту сообщение, сообщающее о том,
что воркер закончил работу, и содержащее последние сообщения, полученные
от воркера, если таковые имеются.
- Если сообщение об успешном вводе в воркер получено -- отправляем
соответствующее сообщение клиенту.
6. Работа воркера закончилась в результате успешного выполнения команды,
или было прервано клиентом командой stop, или по причине ошибок, возникших
при выполнении скриптов команды. Далее:
* Воркер отправляет на сервер одно из двух специальных сообщений,
оповещающих об окончании работы:
- Содержащее также информацию об ошибках (9b), если таковые
были, неотправленных по каким-то причинам сообщениях (?) или о факте
выполнения команды stop.
- Содержащие только объявление об окончании выполнения команды, то есть
состояния run.
В зависимости от того, возможно ли чтение из сокета, когда процесс воркера
уже закрыт сервер будет или не будет отправлять сообщение разрешающее
воркеру прекратить работу, но клиент его видеть не должен. По факту
получения конечного сообщения и, возможно, данных вместе с ним, клиенту
будет присылаться специальное сообщение.
Контекст воркера
----------------
worker_state -- состояние воркера;
socket_context -- контекст чтения сокета воркера;
worker_output -- вывод воркера перед его выключением сервера.
Что делать
----------
[*] Проверить возможность чтения сервером данных из сокета уже закрытого
воркера. Если такой возможности нет, предусмотреть обработку завершения и
успешной, и некорректной работы воркера с передачей всех необходимых
сообщений. Сделать это внутри воркера.
Итог: чтение из сокета убитого демона возможно.
[ ] Добавить в воркер цикл конфигурации или его тестовый вариант;
[*] Убрать на стороне интерфейса клиента лишний шаг создания объекта воркера;
[*] Убрать self._runner_process и self._pid_file, они в таком случае не нужны;
[*] PID-файл демона удаляем сразу после чтения;
[ ] Реализовать описанный ниже протокол взаимодействия сервера с воркером;
[ ] Добавить в метод kill воркера чтение всего содержимого сокета перед его
удалением.
[ ] Разработать HAL-формат сообщений об ошибках вместо HTTPException.
[*] Сделать метод read воркера асинхронным.
[ ] На случай если воркер убит через SIGKILL -- поскольку обработку
такого завершения работы воркера в самом воркере организовать невозможно,
добавить дополнительную проверку is_alive воркера и предусмотреть обработку
этой ситуации.
[*] Добавить главный блок try-catch-finally в воркер. В раздел finally добавить
отправку сообщения об окончании работы воркера. В зависимости от
возможности читать данный из сокета законченного процесса воркера, ждать
или не ждать ответ сервера по поводу окончания работы воркера;
[*] Добавить предварительно деление воркеров по состояниям, вероятно добавить
WorkersManager;
[ ] Добавить состояния воркера:
- config -- воркер в состоянии конфигурации, Т.е. ждёт значения
параметров или собщение об окончании конфигурации;
- exec -- воркер в процессе выполнения команды;
- input -- воркер в процессе ожидания ввода пользователя;
- finish -- воркер в состоянии завершения некоторого состояния.
[ ] Реализовать предварительно REST API в соответствии с наработками;
[*] Решить, какое значение должны иметь заголовки Content-Type и Accept:
- application/json;
- application/hal+json;
- application/vnd.cphl+json;
- application/vnd.api+json.
Подумать над версионированием API;
[ ] Добавить в ответы сервера обозначения о кэшируемости и некэшируемости,
проработать кэширование в целом, определить, что должно кэшироваться, а что
нет;
[ ] Проработать механизм остановки взаимодействия сервера с клиентом и
возвращения к нему после переподключения клиента, перезагрузки сервера, а
также при этих обоих событиях;
[ ] Добавить проверку во время инициализации сервера наличия PID-файлов
демонов-воркеров:
- Если такие есть -- проверить, что это за процессы;
- Если такие процессы не существуют или не являются воркерами -- удалить
эти PID-файлы;
- Если процессы существуют, являются воркерами и есть информация о них
в БД -- проверяем соответствие PID и WID.
-- Если все совпадает -- удаляем PID-файлы и далее создаем объекты
воркеров по информации в БД;
-- [Q#3] Если есть несовпадения -- убиваем воркер, PID-файл и
записи в БД;
- [Q#2] Если процессы существуют, являются воркерами, но в БД о них нет
информации -- наверное, удаляем PID-файлы и пытаемся убить этих
воркеров с помощью их PID.
Интерфейс сервера
-----------------
REST-интерфейс (вроде). Представляем состояния воркеров в виде ресурсов или
объектов. В соответствии с HATEAOS присылаем URI на ресурсы и действия воркера,
которые доступны в тот или иной момент. Для этого применяем формат
json-сообщений напоминающий HAL или CPHL (не очень распространенный формат).
Различие от HAL в наличии атрибута "methods" в "_links".
* root -- корневой ресурс, являющийся точкой входа для всего API. Возможно,
стоит создать алиас /index или заменить на него.
- GET / -- получить доступные корневые ресурсы;
* request:
null
* response:
Media-Type: application/hal+json
Cache-Control: public
{
"data": {
// Some information about server.
},
"_links": {
"commands": {
"href": "/commands/"
},
"workers": {
"href": "/workers/"
}
}
}
{
"meta": {
// Server info
},
"links": {
}
}
* сommands -- совокупность данных о командах, доступных для запуска на
сервере.
- GET /commands/ -- получить список команд;
* request:
Media-Type: application/json
{
"gui": {true/false}
}
* response:
Media-Type: application/hal+json
Cache-Control: private
{
"{command_id}": {
"data": {
// Some command info.
},
"_links": {
"self": {
"href": "/commands/{CID}"
},
"parameters": {
"href": "/commands/{CID}/parameters"
},
"cofigure": {
"href": "/configs/{CID}"
}
}
},
...
}
- GET /commands/{command}/ -- получить информацию об указанной команде, a
также ее параметры. Запрос используемый
консольным клиентом;
* request:
null
* response:
Media-Type: application/hal+json
Cache-Control: private
{
"data": {
// Some command info.
},
"_links": {
"self": {
"href": "/commands/{CID}"
},
"parameters": {
"href": "/commands/{CID}/parameters"
}
"cofigure": {
"href": "/configuration/{CID}"
}
},
"_embedded": {
"parameters": {
"data": [
{
"group_id": "{group_id}",
"parameters": [...]
},
...
],
"_links":
"self": {
"href": "/commands/{CID}/parameters"
}
}
}
}
- GET /commands/{CID}/parameters -- получить параметры указанной команды;
* request:
null
* response:
{
"data": [
{
"group_id": "{group_id}",
"parameters": [...]
},
...
],
"_links": {
"self": {
"href": "/commands/{CID}/parameters"
}
}
}
* configs -- совокупность объектов воркеров, находящихся в состоянии
конфигурации.
- POST /configs/{command} -- создать конфигурацию;
201 -- конфигурация уже создана;
404 -- команды с указанным id нет;
400 -- неправильный формат запроса.
* request:
null
* response:
{
"_links": {
"configure": {
"href": "/configs/{WID}/parameters"
},
"execute": {
"href": "/executions/{WID}"
},
"cancel": {
"href": "/configs/{WID}"
}
}
}
- PATCH /configs/{WID}/parameters -- изменить конфигурацию
указанными в теле значениями;
404 -- конфигурации с указанным
WID нет;
400 -- неправильный формат
запроса.
* request:
[
{"id": "{param_id}", "value": "{param_value}"},
...
]
* response:
{
"data": {
"{param_id}": "{error_message}",
...
},
"_links": {
"configure": {
"href": "/configs/{WID}/parameters"
},
"cancel": {
"href": "/configs/{WID}"
}
}
}
OR
{
"_links": {
"configure": {
"href": "/configs/{WID}/parameters"
},
"execute": {
"href": "/executions/{WID}"
},
"cancel": {
"href": "/configs/{WID}"
}
}
}
- PUT /configs/{WID}/parameters -- заменить конфигурацию с
использованием указанных в теле
запроса значений. Пустой набор
значений позволяет сбросить
конфигурацию;
* request:
[
{"id": "{param_id}", "value": "{param_value}"},
...
]
* response:
{
"data": {
"{param_id}": "{error_message}",
...
},
"_links": {
"configure": {
"href": "/configs/{WID}/parameters"
},
"cancel": {
"href": "/configs/{WID}"
}
}
}
OR
{
"_links": {
"configure": {
"href": "/configs/{WID}/parameters"
},
"execute": {
"href": "/executions/{WID}"
},
"cancel": {
"href": "/configs/{WID}"
}
}
}
- DELETE /configs/{WID} -- закончить конфигурации без выполнения
команды.
* request:
null
* response:
[
{
"data": {
"type": "output",
"logging": {logging_level/null},
"text": "{message_text}",
"source": "{source_script}",
}
},
...
]
* executions -- совокупность объектов воркеров, находящихся в состоянии
исполнения.
- POST /executions/{WID} -- создать новое исполнение из конфигурации;
201 -- исполнение создано;
409 -- исполнение с этим WID уже есть;
400 -- неправильный формат запроса.
404 -- конфигурация не найдена.
* request:
null
* response:
- Response Code: 201
{
"_links": {
"output": {
"href": "/executions/{WID}/output"
},
"stop": {
"href": "/executions/{WID}"
}
}
}
- DELETE /executions/{WID} -- удалить исполнение, происходит путем
отправки в скрипт SIGINT;
* request:
null
* response (возвращает список непереданных сообщений, если они есть):
[
{
"data": {
"type": "output",
"logging": {logging_level/null},
"text": "{message_text}",
"source": "{source_script}",
}
},
...
]
- GET /executions/{WID}/output -- получить список имеющихся сообщений от
команды;
* request:
null
* response (необходимость остановить выполнение определяется по
Response Code):
[
{
"data": {
"type": "output",
"logging": {logging_level/null},
"text": "{message_text}",
"source": "{source_script}",
}
},
{
"data": {
"type": "input",
"text": "{input_message}"
"source": "{source_script}"
},
"_links": {
"input": {
"href": "/executions/{WID}/input",
}
}
}
]
- PATCH /executions/{WID}/input -- отправить в воркер некоторый ввод;
* request (если null, значит ввод прерывается):
{
"data": {input_data/null}
}
* response:
- Response Code: 200
null
- Response Code: 404
{
"error": "Execution {WID} is not found"
}
- Response Code: 400
{
"error": "Input message is not correct"
}
* workers -- вся совокупность воркеров.
- GET /workers/ -- получить список демонов-воркеров и информацию о них;
* request:
null
* response:
{
"{WID}": {
"data": {
"socket": "{socket_path}",
"command": "{worker_command}",
"pid": "{daemon_pid}",
}
"_links": {
"kill": {
"href": "/workers/{WID}",
"methods": ["delete"]
}
}
},
...
}
- DELETE /workers/{WID} -- отправить сигнал SIGKILL демону-воркеру;
* request:
null
* response (сообщения полученные от убитого воркера):
[
{
"data": {
"type": "output",
"logging": {logging_level/null},
"text": "{message_text}",
"source": "{source_script}",
}
},
...
]
Протокол взаимодействия воркера и сервера
-----------------------------------------
Все сообщения представляют собой json-структуры, включающие в себя 3 поля:
- state -- состояние воркера:
* Если сообщение пришло воркеру от сервера, тогда это поле указывает
воркеру, какое состояние он должен принять;
* Если сообщение пришло серверу от воркера, тогда это поле указывает на
текущее состояние воркера.
- status -- статус текущего состояния:
0 -- состояние корректно;
1 -- состояние некорректно.
- data -- некоторые данные, структура которых может быть любой.
1. Данные конфигурации от клиента.
a. Продолжить конфигурацию указанными значениями:
{
"state": "config",
"status": 0,
"data": [
{"id": "{param_id}", "value": "{parameter_value}"},
...
]
}
b. Сбросить конфигурацию и продолжить указанными значениями:
{
"state": "config",
"status": 1,
"data": [
{"id": "{param_id}", "value": "{parameter_value}"},
...
]
}
2. Ответ воркера по данным конфигурации.
a. Все параметры конфигурации корректны:
{
"state": "config",
"status": 0,
"data": null
}
b. Некоторые параметры конфигурации некорректны:
{
"state": "config",
"status": 1,
"data": {
"{param_id}": "{error_message}",
...
}
}
3. Запрос на окончание конфигурации от клиента.
a. Закончить конфигурацию, начать выполнение команды:
{
"state": "finish",
"status": 0,
"data": null
}
b. Закончить конфигурацию и работу воркера:
{
"state": "finish",
"status": 1,
"data": null
}
4. Ответ воркера на запрос об окончании конфигурации:
a. Закончить конфигурацию, начать выполнение команды:
{
"state": "finish",
"status": 0,
"data": null
}
b. Закончить конфигурацию и работу воркера:
{
"state": "finish",
"status": 1,
"data": {
"{param_id}": "{error_message}",
...
}
}
5. Сообщение с выводом воркера.
a. Сообщение из скрипта при его "штатной" работe:
{
"state": "exec",
"status": 0,
"data": {
"type": "message",
"logging": {logging_level/null},
"text": "{message_text}",
"source": "{source_script}"
}
}
b. Сообщение об ошибке приводящей к завершению выполнения скрипта и
команды:
{
"state": "exec",
"status": 1,
"data": {
"error": "{error_message}",
"source": "{source_script}"
}
}
6. Сообщение с запросом на ввод.
a. Состояние "input" -- воркер в состоянии ожидания ввода:
{
"state": "input",
"status": 0,
"data": {
"text": "{message_text}",
"source": "{source_script}"
}
}
7. Сообщение от клиента с данными.
a. Ввод успешно выполнен и передается:
{
"state": "input",
"status": 0,
"data": "{client_data}"
}
b. Ввод данных прерван, данных не будет:
{
"state": "input",
"status": 1,
"data": null
}
8. Сообщение от воркера о получении ввода.
a. Ввод успешно получен, о чем говорит переход в состояние выполнения со
статусом 0, т.е. без ошибок:
{
"state": "exec",
"status": 0,
"data": null
}
9. Сообщение воркера об окончании работы:
a. Успешное окончание работы:
{
"state": "finish",
"status": 0,
"data": null
}
b. Работа воркера была прервана или закончилась с ошибками:
{
"state": "finish",
"status": 1,
"data": [
"{error_message}",
...
]
}