Для чего это нужно?
Потребность автоматизации процессов администрирования и мониторинга серверов 1С, а именно:
- Мониторинг (снятие счетчиков, доступности и блокировки ИБ)
- Добавление/удаление ИБ
- Перемещение ИБ
- Добавление рабочих серверов (настройка их по шаблону)
- Блокировка и отключение сеансов/зависших соединений
- Подсчет количества лицензий
- И прочие рутинные действия
Как это сейчас работает?
Сейчас для автоматизации процессов администрирования серверов 1С используется следующие схемы работы:
- Служба `ras` и клиент `rac`, а так же библиотеки обертки, которые парсят выхлоп клиента `rac`
- Com подключение - и различные обертки для него
Все эти инструменты требуют установленной 1С, что является их существенным недостатком.
В различных проектах используется “зоопарк” из этих решений и его поддержка занимает уже много времени, а переход на один из инструментов требует существенной доработки уже существующих механизмов и доработки самих инструментов, а самое главное не будет масштабируемым из-за общего недостатка всех этих решений, а именно требуется установленная 1С.
Что будем делать?
План решения задачи
- Для начала предлагается сделать описание всех данных, которые необходимы для работы с серверами 1С.
- Научиться их сериализовать в формат RAS и читать из него, для прямого общения с 1С минуя прослойку в виде клиента `rac`, чем мы устраняем основной недостаток - установленная 1С.
- Реализовать единый интерфейс для всех автоматизаций по протоколу HTTP/gRPC с автоматической генерацией клиентов под любой язык программирования.
Например, для веб-сайта необходимо реализовать клиента на JS, для какой-то другой программы, возможно, потребуется требуется клиент на golang или C#, и все должно быть единообразно, клиенты под любой язык программирования должны создаваться с помощью кодогенерации на основе описания протокола RAS
- Создать масштабируемый микросервис для решения задач администрирования и его быстрого развертывания в docker и kubernetes
Как будем описывать формат данных
Для описания данных клиентов и серверов, а также данных получаемых от RAS предлагается использовать protobuf , как единый формат описания
Это позволит нам создавать клиентов (к RAS и/или нашему единому интерфейсу по протоколам HTTP/gRPC) под любые языки разработки с минимальными трудозатратами. C помощью плагина для генерации кодирования/декодирования в формат RAS все описанные форматы данных можно будет легко кодировать и декодировать для прямого общения с RAS.
Немного специфики:
- Сообщение (message) - в формате protobuf - это класс/структура в других языках.
- Для определения специфичности формата RAS мы будет использовать расширение данных сообщений, эти данные указаны как option или выделены в [ … ]
Описание формата данных RAS
На прямую формат данных описываться не будет, но будут указаны порядок сериализации и кодек который надо применить к тому или иному полю.
Эти данные можно использовать для кодирования в формат RAS в языках разработки.
Здесь будет приведены примеры для языка golang. (для java - можно взять из предоставленной 1С либы)
За основу разработки формата данных взята Java - либа от 1С, и переработана в Golang/protobuf.
Схема работы клиента RAS
В статье будет рассмотрена следующая общая схема работы клиента RAS
Подготовка данных для сериализации в формат RAS
Для начала необходимо понять, что все сообщения к RAS и при получении ответа упакованы в специальный пакет данных. Для его описания мы создадим сообщение `packet`
Для декодирования нам необходимо ввести две дополнительные опции:
-
type_field - указание на номер поля в котором содержится тип данных
-
size_field - поле в котором указан размер данных для поля с типом bytes
Packet
Структура данных:
Название |
Тип |
Порядок |
Кодек |
Описание |
type |
PacketType |
1 |
byte |
Определяет тип посылаемого пакета, задается в виде перечисления PacketType |
size |
int32 |
2 |
size |
Задает размер получаемых и передаваемых данных в пакете |
data |
bytes |
3 |
bytes |
Данные передаваемые в пакете |
Получившееся описание
message Packet {
PacketType type = 1 [(ras.encoding.field) = {
encoder: "byte",
order: 1,
}
];
int32 size = 2 [(ras.encoding.field) = {
encoder: "size",
order: 2,
}];
bytes data = 3 [(ras.encoding.field) = {
encoder: "bytes",
order: 3,
type_field: 1,
size_field: 2,
}];
}
PacketType
Содержит и определяет возможное типы передаваемых пакетов. Для простоты загоняем все возможные типы в перечисление и даем им более понятные названия, часть типов пакетов мы реализовывать не будет, по этому зарезервируем их номера и имена.
для быстрого получения связанного с типом сообщения используем расширение значение перечисления "type_url".
Получившееся описание типов
enum PacketType {
PACKET_TYPE_NEGOTIATE = 0 [(ras.encoding.type_url) = "ras.protocol.v1.NegotiateMessage"];
PACKET_TYPE_CONNECT = 1 [(ras.encoding.type_url) = "ras.protocol.v1.ConnectMessage"];
PACKET_TYPE_CONNECT_ACK = 2 [(ras.encoding.type_url) = "ras.protocol.v1.ConnectMessageAck"];
PACKET_TYPE_DISCONNECT = 4 [(ras.encoding.type_url) = "ras.protocol.v1.DisconnectMessage"];
reserved 3, 5 to 10;
reserved "PACKET_TYPE_START_TLS", "PACKET_TYPE_SASL_NEGOTIATE", "PACKET_TYPE_SASL_AUTH", "PACKET_TYPE_SASL_CHALLENGE";
reserved "PACKET_TYPE_SASL_SUCCESS", "PACKET_TYPE_SASL_FAILURE", "PACKET_TYPE_SASL_ABORT";
PACKET_TYPE_ENDPOINT_OPEN = 11 [(ras.encoding.type_url) = "ras.protocol.v1.EndpointOpen"];
PACKET_TYPE_ENDPOINT_OPEN_ACK = 12 [(ras.encoding.type_url) = "ras.protocol.v1.EndpointOpenAck"];
PACKET_TYPE_ENDPOINT_CLOSE = 13 [(ras.encoding.type_url) = "ras.protocol.v1.EndpointOClose"];
PACKET_TYPE_ENDPOINT_MESSAGE = 14 [(ras.encoding.type_url) = "ras.protocol.v1.EndpointMessage"];
PACKET_TYPE_ENDPOINT_FAILURE = 15 [(ras.encoding.type_url) = "ras.protocol.v1.EndpointFailure"];
PACKET_TYPE_KEEP_ALIVE = 16;
}
Используемые кодеки
- byte - кодирует/декодирует 1 байт
- size - кодирует/декодирует размер в формат int32, сложный кодек с последовательным чтение байтов и применением к ним бинарных операций.
- bytes - кодирует/считывает массив байтов нужного размера или до окончания сообщения, если размер не задан.
На этом подготовка данных для сериализации закончена и можно приступить к установке соединения с RAS
Установка соединения с RAS
Для работы на прямую с RAS необходимо установить соединение по tcp, для каждого языка это свои стандартные пакеты и тут описывать их смысла нет, поэтому я думаю каждый программист в состоянии установить подключение tcp для своего языка.
После подключения по tcp к RAS, служба ожидает магическое сообщение NegotiateMessage.
Особенности этого сообщения в том, что оно единственное не требует упаковки в сообщение packet
Структура данных NegotiateMessage :
Название |
Тип |
Порядок |
Кодек |
Описание |
magic |
int32 |
1 |
int32 |
Магическое число, которые всегда равно 475223888 |
protocol |
int32 |
2 |
short |
Всегда равно 256 |
version |
int32 |
3 |
short |
По идеи это указание версии кодека, но мне известен только значение 256 |
Получившее описание сообщения:
message NegotiateMessage {
option (packet_type) = PACKET_TYPE_NEGOTIATE;
int32 magic = 1 [(ras.encoding.field) = {
order: 1,
encoder: "int32",
}]; // Use only one value `475223888`
int32 protocol = 2 [(ras.encoding.field) = {
order: 2,
encoder: "short",
}];
int32 version = 3 [(ras.encoding.field) = {
order: 3,
encoder: "short",
}];
}
Тут появилась специальная опция `packet_type` которая связывает с типом (PacketType) который будет установлен или прочитан в сообщении `packet`, это поле используется для сериализации в RAS
Используемые кодеки
- int32 - кодирует/декодирует полученное число в 4 байта в формате BigEndian
- short - кодирует/декодирует полученное число в 2 байта в формате BigEndian
ВАЖНО! Этот сообщение надо сериализовать (кодировать) в байты и записать на прямую в подключение и он не требует чтения ответа от RAS. В случае любой ошибки в этом сообщении RAS просто закрывает соединение.
Инициализируем подключение отослав сообщение ConnectMessage
Структура данных ConnectMessage:
Название |
Тип |
Порядок |
Кодек |
Описание |
params |
map<string, Param> |
1 |
map |
Передаются параметры подключения на текущий момент известны "connect.timeout" = 2000 |
Структура данных Param:
Название |
Тип |
Порядок |
Кодек |
Описание |
type |
ParamType |
1 |
byte |
Тип передаваемого параметра |
value |
bytes |
2 |
bytes |
Сериализованные данные параметра |
Получившее описание сообщений:
message ConnectMessage {
option (packet_type) = PACKET_TYPE_CONNECT;
// Известные параметры
// "connect.timeout" = 2000
map<string, Param> params = 2 [(ras.encoding.field).order = 1];
}
Тут важно отметить, что передача параметров подключения необязательно.
После отправки данного сообщения необходимо дождаться ответного сообщения от RAS ConnectMessageAck
Структура данных ConnectMessageAck:
Название |
Тип |
Порядок |
Кодек |
Описание |
params |
map<string, Param> |
1 |
map |
Параметры подключения |
Получившее описание сообщения:
Собственно на этом первая часть закончена, мы описание подготовку и сообщения необходимые для подключения и начала работы с RAS на прямую
Используемые кодеки
- map - кодирует/декодирует структуру. Если она пустая то обязательно необходимо закодировать размер 0 с помощью кодека `size`
В следующей части
- Открытие точки обмена (endpoint)
- Формирование запросов и их типы (GetClustersRequest)
- Общие описания данных (ClusterInfo)
Материалы, где можно посмотреть и "пощупать" результат
- https://github.com/v8platform/protos/extra/encoding/rasbinary - кодер и декодер в формат ras
- https://github.com/v8platform/protos/rasapis/ras/protocol - описание сообщений протокола ras
- https://github.com/v8platform/protos/example/ras-client - простейший клиент ras разработанный на основе описанного материала
- https://github.com/v8platform/protos/example/grpc-server - сервер grpc для клиента ras, с использованием rest-gateway (формирование запросов по http)
- Protobuf - используется для генерации кода (общие структуру, клиент и сервер grpc)