Прикладные роли
Прикладная роль — это Lua-модуль, реализующий определенные функции или логику. Роль можно включать и отключать для конкретных экземпляров конфигурации без их перезапуска. Роль запускается при загрузке или перезагрузке конфигурации.
Роли можно разделить на следующие группы:
- Встроенные роли Tarantool. Например, роль
config.storageпозволяет использовать набор реплик Tarantool в качестве хранилища конфигурации. - Роли, предоставляемые сторонними Lua-модулями. Например, модуль
CRUD предоставляет роли
roles.crud-storageиroles.crud-router, которые включают CRUD-операции в шардированном кластере. - Пользовательские роли, разрабатываемые как часть кластерного приложения. Например, можно создать пользовательскую роль для определения хранимой процедуры или реализации вспомогательного сервиса, такого как почтовый нотификатор или репликатор.
В этом разделе описывается разработка пользовательских ролей. Подробнее о включении и настройке ролей см. configuration_application_roles.
Пользовательская роль настраивается так же, как роли, предоставляемые Tarantool или сторонними Lua-модулями. Подробнее см. Включение и настройка ролей.
В примере ниже показано, как включить и настроить роль greeter, реализация которой приведена в следующем разделе:
instance001:
Конфигурация роли, заданная в roles_cfg, доступна
при валидации и применении этой конфигурации.
В состав Tarantool входит встроенный модуль experimental.config.utils.schema,
предоставляющий инструменты для управления пользовательскими конфигурациями приложений (app.cfg) и ролей (roles_cfg).
В примерах ниже показано его базовое использование.
Поскольку роль является Lua-модулем, имя роли
передается в require() для получения модуля.
При разработке приложения файл с кодом
роли можно поместить рядом с файлом конфигурации кластера.
Пользовательская прикладная роль — это объект, реализующий пользовательские функции или логику в дополнение к встроенным ролям Tarantool и ролям из сторонних Lua-модулей. Например, можно создать роль логирования, чтобы добавить функциональность логирования поверх встроенной.
Создание пользовательской роли включает следующие шаги:
- (Необязательно) Определить схему конфигурации роли.
- Определить функцию, которая валидирует конфигурацию роли.
- Определить функцию, которая применяет провалидированную конфигурацию.
- Определить функцию, которая останавливает роль.
- (Необязательно) Определить роли, от которых зависит данная пользовательская роль.
- (Необязательно) Определить функцию обратного вызова
on_event.
В результате модуль роли должен возвращать объект с соответствующими функциями и полями:
return {validate = function() -- ... -- end,apply = function() -- ... -- end,stop = function() -- ... -- end,dependencies = { -- ... -- },on_event = function(config, key, value)local log = require('log')log.info('roles_cfg.my_role.foo: ' .. config.foo)log.info('on_event is triggered by ' .. key)log.info('is_ro: ' .. value.is_ro)end,}
В примерах из этой статьи показано, как это сделать.
Необязательные шаги можно пропустить и получить простую роль, как в примере ниже.
return {validate = function() -- ... -- end,apply = function() -- ... -- end,stop = function() -- ... -- end,}
Роль можно изменять, например, добавляя зависимости или определяя
функцию обратного вызова on_event. После изменения роли необходимо
перезапустить экземпляр Tarantool с этой ролью, чтобы применить
изменения.
Встроенный модуль experimental.config.utils.schema предоставляет класс config-utils-schema_object. Объект этого класса определяет пользовательскую схему конфигурации роли или приложения.
В примере ниже показано, как определить схему, отражающую конфигурацию роли, приведенную выше:
local greeter_schema = schema.new('greeter', schema.record({greeting = schema.scalar({type = 'string',allowed_values = { 'Hi', 'Hello' }})}))
Если модуль не используется, пропустите этот шаг. В этом случае для
обращения к значениям конфигурации роли используйте аргумент cfg
функций validate() и apply(), например, cfg.greeting.
Для валидации конфигурации роли необходимо определить функцию validate().
В примере ниже функция validate() схемы конфигурации роли используется
для валидации значения greeting:
local function validate(cfg)greeter_schema:validate(cfg)end
Если конфигурация недействительна, validate() сообщает о неисправимой
ошибке, выбрасывая объект ошибки.
Для применения провалидированной конфигурации определите функцию apply().
Как и функция validate(), apply() предоставляет доступ к конфигурации роли через аргумент cfg.
В примере ниже функция apply() использует модуль log для
записи значения из конфигурации роли в лог:
local function apply(cfg)log.info("%s from the 'greeter' role!", greeter_schema:get(cfg, 'greeting'))end
Для остановки роли используйте функцию stop().
В примере ниже функция stop() использует модуль log,
чтобы указать, что роль остановлена:
local function stop()log.info("The 'greeter' role is stopped")end
После определения всех функций роли необходимо вернуть объект с соответствующими функциями:
return {
Для определения зависимостей роли используйте поле dependencies.
В этом примере роль byeer имеет роль greeter в качестве зависимости:
-- byeer.lua --local log = require('log').new("byeer")return {dependencies = { 'greeter' },validate = function() end,apply = function() log.info("Bye from the 'byeer' role!") end,stop = function() end,}
Роль не может быть запущена без своих зависимостей. Это означает, что
все зависимости роли должны быть указаны в конфигурационном параметре
roles:
instance001:roles: [ greeter, byeer ]
Полный пример доступен здесь: application_role_cfg.
Начиная с версии 3.3.1,
для пользовательских ролей можно определять функцию обратного вызова
on_event. Функция обратного вызова on_event вызывается при каждом
широковещательном системном событии box.status. Если функция обратного
вызова on_event определена в нескольких пользовательских ролях, эти
функции вызываются последовательно в порядке, определяемом зависимостями
ролей.
Функция обратного вызова on_event принимает 3 аргумента при вызове:
-
config— содержит конфигурацию роли; -
key— отражает событие-триггер и принимает следующие значения:config.apply— если функция обратного вызова вызвана при обновлении конфигурации;box.status— если функция вызвана системным событиемbox.status.
-
value— содержит информацию о статусе экземпляра, как в системном событии-триггереbox.status. Если функция обратного вызова вызвана при обновлении конфигурации,valueсодержит информацию о последнем системном событииbox.status.
Пример функции обратного вызова on_event приведен в разделе Создание спейсов ниже.
Код инициализации можно добавить в роль, определив и вызвав функцию с произвольным именем на верхнем уровне модуля, например:
local function init()-- ... --endinit()
Например, можно создавать спейсы, определять индексы или назначать привилегии конкретным пользователям или ролям.
См. также: Особенности создания спейсов.
Для создания спейса в роли необходимо убедиться, что целевой экземпляр
находится в режиме чтения-записи
(значение box.info.ro равно false).
Проверить состояние экземпляра можно, подписавшись на событие box.status с
помощью box.watch():
box.watch('box.status', function()-- creating a space-- ...end)
Начиная с версии 3.3.1, создание спейсов в роли можно определять через функцию обратного вызова on_event.
Пример такого определения приведен ниже:
return {validate = function() end,apply = function() end,stop = function() end,on_event = function(config, key, value)-- Can only create spaces on RW.if value.is_ro thenreturnend-- Assume the role config is a table.if type(config) ~= 'table' thenerror('Config must be a table')endlocal space_name = config.space_name or 'default'box.schema.space.create(space_name, {if_not_exists = true,})end}
Жизненный цикл роли включает описанные ниже этапы.
-
Загрузка ролей
При каждом запуске все роли загружаются в порядке, указанном в конфигурации. Этот этап действует при включении роли или перезапуске экземпляра с этой ролью. На этом этапе роль выполняет код инициализации.
Роль не может быть запущена, если у нее есть зависимости, не указанные в конфигурации.
-
Остановка ролей
Этот этап действует при перезагрузке конфигурации, когда роль удаляется из конфигурации для данного экземпляра. Обратите внимание, что все вызовы
stop()выполняются до любых вызововvalidate()илиapply(). Это означает, что сначала останавливаются старые роли и только затем запускаются новые. -
Валидация конфигураций ролей
На этом этапе конфигурация каждой роли валидируется с помощью соответствующей функции validate() в том же порядке, в котором роли указаны в конфигурации.
-
Применение конфигураций ролей
На этом этапе конфигурация каждой роли применяется с помощью соответствующей функции apply() в том же порядке, в котором роли указаны в конфигурации.
Все функции роли сообщают о неисправимой ошибке, выбрасывая объект
ошибки. Если на любом этапе возникает ошибка, применение конфигурации
прекращается. Если при запуске или остановке роли возникает ошибка,
последующие роли не останавливаются и не запускаются.
Ошибка перехватывается и отображается
в config:info() в разделе alerts.
Для ролей, зависящих друг от
друга, функции validate(), apply() и stop() выполняются с учетом
зависимостей. Предположим, есть три независимые и две зависимые роли:
role1role2role3└─── role4└─── role5
-
role1,role2иrole5— независимые роли. -
role3зависит отrole4,role4зависит отrole5.
Роли включены в конфигурации следующим образом:
roles: [ role1, role2, role3, role4, role5 ]
В этом случае validate() и apply() для этих ролей выполняются в
следующем порядке:
role1 -> role2 -> role5 -> role4 -> role3
Роли, удаленные из конфигурации, останавливаются в порядке, обратном
порядку их указания в конфигурации, с учетом зависимостей. Предположим,
из конфигурации выше удалены все роли, кроме role1:
roles: [ role1 ]
После перезагрузки конфигурации функции stop() для удаленных ролей
выполняются в следующем порядке:
role3 -> role4 -> role5 -> role2
В примере ниже показано, как включить пользовательскую роль greeter
для instance001:
instance001:roles: [ greeter ]
Реализация этой роли выглядит следующим образом:
-- greeter.lua --return {validate = function() end,apply = function() require('log').info("Hi from the 'greeter' role!") end,stop = function() end,}
Пример на GitHub: application_role
В примере ниже показано, как включить пользовательскую роль greeter
для instance001 и задать конфигурацию для этой роли:
instance001:roles: [ greeter ]roles_cfg:greeter:greeting: 'Hi'
Реализация этой роли выглядит следующим образом:
local greeter_schema = schema.new('greeter', schema.record({greeting = schema.scalar({type = 'string',allowed_values = { 'Hi', 'Hello' }})}))
local function validate(cfg)greeter_schema:validate(cfg)end
local function apply(cfg)log.info("%s from the 'greeter' role!", greeter_schema:get(cfg, 'greeting'))end
local function stop()log.info("The 'greeter' role is stopped")end
return {
Пример на GitHub: application_role_cfg
В примере ниже показано, как включить и настроить пользовательскую роль
http-api:
instance001:roles: [ http-api ]roles_cfg:http-api:host: '127.0.0.1'port: 8080
Реализация этой роли выглядит следующим образом:
-- http-api.lua --local httpdlocal json = require('json')local schema = require('experimental.config.utils.schema')local function validate_host(host, w)local host_pattern = "^(%d+)%.(%d+)%.(%d+)%.(%d+)$"if not host:match(host_pattern) thenw.error("'host' should be a string containing a valid IP address, got %q", host)endendlocal function validate_port(port, w)if port <= 1 or port >= 65535 thenw.error("'port' should be between 1 and 65535, got %d", port)endendlocal listen_address_schema = schema.new('listen_address', schema.record({host = schema.scalar({type = 'string',validate = validate_host,default = '127.0.0.1',}),port = schema.scalar({type = 'integer',validate = validate_port,default = 8080,}),}))local function validate(cfg)listen_address_schema:validate(cfg)endlocal function apply(cfg)if httpd thenhttpd:stop()endlocal cfg_with_defaults = listen_address_schema:apply_default(cfg)local host = listen_address_schema:get(cfg_with_defaults, 'host')local port = listen_address_schema:get(cfg_with_defaults, 'port')httpd = require('http.server').new(host, port)local response_headers = { ['content-type'] = 'application/json' }httpd:route({ path = '/band/:id', method = 'GET' }, function(req)local id = req:stash('id')local band_tuple = box.space.bands:get(tonumber(id))if not band_tuple thenreturn { status = 404, body = 'Band not found' }elselocal band = { id = band_tuple['id'],band_name = band_tuple['band_name'],year = band_tuple['year'] }return { status = 200, headers = response_headers, body = json.encode(band) }endend)httpd:route({ path = '/band', method = 'GET' }, function(req)local limit = req:query_param('limit')if not limit thenlimit = 5endlocal band_tuples = box.space.bands:select({}, { limit = tonumber(limit) })local bands = {}for _, tuple in pairs(band_tuples) dolocal band = { id = tuple['id'],band_name = tuple['band_name'],year = tuple['year'] }table.insert(bands, band)endreturn { status = 200, headers = response_headers, body = json.encode(bands) }end)httpd:start()endlocal function stop()httpd:stop()endlocal function init()require('data'):add_sample_data()endinit()return {validate = validate,apply = apply,stop = stop,}
Пример на GitHub: application_role_http_api
Элементы | |
|---|---|
Валидация конфигурации роли. | |
Применение конфигурации роли. | |
Остановка роли. |
Валидация конфигурации роли. Эта функция вызывается при запуске
экземпляра или при перезагрузке конфигурации для экземпляра
с этой ролью. Обратите внимание, что функция validate() вызывается
независимо от того, изменена ли конфигурация роли или какое-либо поле в
конфигурации кластера.
validate() должна выбрасывать ошибку, если валидация не пройдена.
Параметры:
cfg— конфигурация роли, которую нужно провалидировать. Этот параметр предоставляет доступ к параметрам конфигурации, определенным в roles_cfg.<role_name>. Для получения значений параметров конфигурации, находящихся внеroles_cfg.<role_name>, используйте config:get().
См. также: Валидация конфигурации роли.
Применение конфигурации роли. apply() вызывается после выполнения
validate() для всех включенных ролей. Как и функция validate(),
apply() вызывается при запуске экземпляра или при перезагрузке
конфигурации для экземпляра с этой ролью.
apply() должна выбрасывать ошибку, если указанную конфигурацию
невозможно применить.
cfg— конфигурация роли, которую нужно применить. Этот параметр предоставляет доступ к параметрам конфигурации, определенным в roles_cfg Для получения значений параметров конфигурации, находящихся внеroles_cfg.<role_name>, используйте config:get().
См. также: Применение конфигурации роли.
Остановка роли. Эта функция вызывается при перезагрузке конфигурации,
если роль удалена из roles для данного экземпляра.
См. также: Остановка роли.
(Необязательно) Определение зависимостей роли.
Тип возвращаемого значения
table
См. также: Зависимости ролей