YAML-структура процедур
Статья описывает принципы YAML-описания процедур, чтобы выполнять следующие действия:
- Создавать новые процедуры.
- Использовать переменные, условия и действия.
- Обрабатывать ошибки и настраивать логирование.
- Оптимизировать выполнение с помощью встроенных функций.
Общая структура YAML-файла
vars:
# Определение переменных
steps:
# Последовательность шагов
error-handlers:
# Обработчики ошибок
config:
# Настройки процедуры
Переменные vars
Переменные объявляются в корневом блоке верхнего уровня vars.
Базовое определение переменной
vars:
name_var:
type: "тип"
default: "значение_по_умолчанию" # необязательно
visibility: "public|private" # видимость переменной. По умолчанию private
Тип данных
Возможные значения типов данных:
string— текстовая строка;number— целое или дробное число;boolean—trueилиfalse;array— массив["a", "b", "c"];object— набор пар{"key": "value"};null— пустое значение.
Видимость переменных
Возможные значения:
private— доступны только внутри процедуры. Значение по умолчанию;public— можно задать снаружи через команды запуска по расписаниюchecker procedure scheduleили запуска вручнуюchecker procedure execute.
Пример
Подстановка переменных
Используйте {{ имя }} для подстановки значений
vars:
node_name:
type: "string"
default: "node-01"
steps:
step1:
action:
apicall: "cluster nodes show --node {{ node_name }}"
Расширенная подстановка с JMESPath
JMESPath позволяет выполнять сложные операции с данными в шаблонах переменных.
Базовый синтаксис
Основные примеры:
-
Фильтрация массива по условию
-
Доступ к вложенным данным
-
Агрегация данных
-
Агрегация данных
Примечание
Для реализации более сложных процедур введены Расширения JMESPath
Распаковка массивов
vars:
nodes:
type: "array[string]"
default: ["node1", "node2", "node3"]
# В шаблоне
"Проверка узлов: {{*nodes}}"
# Результат:
# "Проверка узла: node1"
# "Проверка узла: node2"
# "Проверка узла: node3"
Совместная распаковка массивов
Внимание
Все массивы должны иметь одинаковую длину, иначе процедура завершится ошибкой
vars:
nodes: ["a", "b"]
ips: ["1.1.1.1", "2.2.2.2"]
# В шаблоне
"{{*nodes}} имеет IP {{*ips}}"
# Результат:
# "a имеет IP 1.1.1.1"
# "b имеет IP 2.2.2.2"
Встроенные переменные
Кроме переменных, определяемых в процедуре, есть встроенные переменные, позволяющие взаимодействовать с контекстом исполнения процедуры.
Встроенные переменные отличаются от обычных префиксом @ перед названием.
@deps
@deps позволяет добавлять зависимые подзадачи к текущей процедуре в конце шага. Все подзадачи должны завершиться перед тем, как процедура сможет перейти к следующему шагу. Эта переменная очищается в начале каждого шага.
Объекты, которые можно добавлять в переменную @deps, должны иметь формат TaskId.
На данный момент в таком формате выводят результат следующие команды:
checker procedure execute --json "yes" <...>.scheduler request reschedule.
@deps_status
@deps_status содержит результаты выполнения всех зависимых подзадач, добавленных в @deps.
Объекты переменной @deps_status имеют следующую структуру:
{
"step": "<название шага, на котором остановилась задача>",
"task_status": "<статус задачи, один из: SUCCESS, TIMEOUT, ERROR>",
"run_error": "<сообщение ошибки выполнения задачи>" | null,
"task_steps_total": "<общее число шагов задачи>",
"run_on_vip": "<выполняется ли задача только на vip-узле>",
"run_on_node": "<uuid узла, на котором задача должна выполняться>" | null,
"task_uuid": "<uuid задачи>",
"task_name": "<название задачи>" | null,
"task_message": "<лог сообщений задачи>" | null,
"worker_node": "<узел, на котором выполнялась задача>",
"created_at": "<дата и время создания задачи>",
"updated_at": "<дата и время последнего обновления задачи>"
// ...другие поля, значение которых на данный момент не задокументировано
}
@result
@resultcодержит результат выполнения действия, очищается в начале каждого шага. Используется для логирования результата выполнения действия без сохранения в отдельную переменную.
Шаги
Процедура разделена на последовательность шагов. Каждый шаг определяется как совокупность условий condition и действия action. Перед исполнением процедуры запускается проверка всех условий. Пока все условия не будут соблюдены, процедура не может перейти к исполнению действия. После исполнения действия, процедура переходит на следующий шаг.
Основная часть условий и действий — это команда. В качестве команды можно указать одно из двух значений:
apicall: делает запрос с синтаксисомsdc-cliи передает результат исполнения команды на обработку вexpect/parseconst: передает указанное значение напрямую вexpect/parse. В качестве значения команды может быть указан либо один шаблон, результат исполнения которого будет передан дальше на обработку, либо список шаблонов. В случае списка шаблонов на обработку будет передан массив с результатами каждого из перечисленных шаблонов.
Пример
Структура шага
steps:
name_step:
conditions: # условия для выполнения
- ...
action: # действие, которое выполнится
...
on-error: # обработчики ошибок для шага
- ...
attempts: # настройки повторных попыток
...
Условия conditions
Перед тем, как перейти к исполнению действия шага action, нужно, чтобы были выполнены все условия conditions.
conditions — это список команд, которые будут запускаться каждый раз при исполнении шага. Все условия шага должны завершиться без ошибки и все выражения в expect должны быть true, после чего шаг перейдет к исполнению действия.
Для обработки результата выполнения команды условия предусмотрено поле expect, которое является списком выражений. Каждое выражение имеет следующую структуру:
conditions:
- apicall: "cluster nodes list" # команда
expect: # проверки результата
- filter: "[?status=='online'] | length(@)" # JMESPath
op: ">=" # операция сравнения
value: 3 # ожидаемое значение
где
filter— выражениеjmespath, обрабатывающее результат исполнения команды. Результат этого выражения передается вop.op— операция сравнения:==,!=,>,>=,<,<=. Левым операндом будет результат исполненияfilter, а правымvalue. Если слева от операции поставить*(*==,*<=, и т.д.), то операция повторится для каждого элемента левого операнда.value— значение, с которымopбудет сравнивать результат исполненияfilter.
Если хотя бы одно выражение вернуло false, то условие считается невыполненным, и шаг повторяется. Пока все условия не выполнены и ни одно из выражений не завершилось с ошибкой, процедура будет проверять текущий шаг. Это поведение можно изменить с помощью attempts.
Пример
vars:
nodes_to_test:
type: "array[string]"
default: ['node1', 'node2']
steps:
step1:
conditions:
- apicall: "cluster nodes list" # [{"status": "online", ...}, ...]
expect:
# Вариант 1. Все узлы, кроме nodes_to_test имеют статус `online`
- filter: "[?!contains({{node_to_test}}, @.name)].status"
op: "*=="
value: "online"
# Вариант 2. Все узлы, кроме nodes_to_test имеют статус `online`
- filter: "[?status=='online'].name"
op: "*!="
value: "{{*nodes_to_test}}"
- apicall: "cluster nodes show --node {{*nodes_to_test}}" # [{"node": "node1", ...}, {"node": "node2", ...}]
expect:
# Узлы из `nodes_to_test` имеют статус `online`
- filter: "[].status"
op: "*=="
value: "online"
Действия action
После того как все условия conditions были выполнены, шаг переходит к действию action.
Действие — это команда, выполняющая определенную операцию и, возможно, обновляющая одну или несколько переменных.
В отличии от expect у условия, для работы с переменными используется поле parse. Формат выражений у parse отличается тем, что вместо проверки результата происходит обработка и сохранение в переменную.
Основные операции, поддерживаемые выражениями в parse:
store— записать значение фильтра в переменную;push— добавить значение в конец массива. Используется только для переменных типаarray;extend— добавить в конец массива все значения массива, полученного в результате исполнения команды. Используется только для переменных типаarrayи результата типаarray.
Пример
Попытки attempts
Попытки attempts ограничивают количество повторных выполнений команды. При необходимости в поле attempts указывается максимальное или минимальное количество попыток перед тем, как команда завершится с ошибкой. Между попытками можно определить минимальный интервал, с которым будут считаться попытки.
Пример
steps:
# Эта команда будет выполнена до трех раз с интервалом в 60 секунд между каждой попыткой при ошибке в действии. Если после третьего исполнения действие все еще завершается с ошибкой, то весь шаг также завершится с последней полученной ошибкой.
step1:
action:
apicall: "<...>"
attempts:
max: 3
interval: 60
# Это условие будет запущенно как минимум 2 раза с интервалом 60 секунд между каждой попыткой. Если во время одной из двух попыток будет ошибка или `expect` вернет false, то условие завершится с ошибкой.
step2:
conditions:
- apicall: "<...>"
expect:
- <...>
attempts:
min: 2
interval: 60
Обработка ошибок
Во время выполнения шага может произойти ошибка: было достигнуто ограничение на количество попыток, во время парсинга вывода произошла ошибка, плагин вернул ошибку, и т.д. Для обработки ошибок предусмотрено общее для всех шагов поле error-handlers. Данное поле содержит команды, которые могут вызываться при ошибках, а также команды, вызываемые по умолчанию для определенных типов ошибок.
Для того, чтобы во время ошибки шага был выбран нужный обработчик ошибок, требуется указать его в поле on-error. Это поле может быть указано для шага, действия или условия, более глубокое указание имеет приоритет. on-error является списком названий обработчиков handler и типов ошибок error-type, для которых нужно использовать данный обработчик. Типы ошибок error-type:
"AttemptsLimitReached"— ошибка, вызываемая при достижении ограничения вattempts;"*"— любой тип ошибки.
Пример
vars:
<...>
steps:
step1:
conditions:
- # <...>
on-error: # выбор нужного обработчика ошибок для условия
- error-type: "AttemptsLimitReached" # тип ошибки — достижение ограничения
handler: "notify" # название обработчика
action:
# <...>
on-error: # выбор нужного обработчика ошибок для действия
- error-type: "*" # любой тип ошибки
handler: "_continue" # название обработчика
on-error: # выбор нужного обработчика ошибок для шага
- error-type: "AttemptsLimitReached" # тип ошибки — достижение ограничения
handler: "notify" # название обработчика
error-handlers:
<...>
Обработчики ошибок могут указывать, что делать после обработки ошибки. Для этого используется поле control-flow со следующими возможными вариантами:
repeat— повторить шаг еще раз;continue— пропустить шаг и перейти к следующему;exit— завершить процедуру с статусомSUCCESS;terminate— завершить процедуры с статусомERROR. Вариант по умолчанию.
Пример
vars:
<...>
steps:
step1:
conditions:
- # <...>
on-error:
- error-type: "AttemptsLimitReached"
handler: "notify"
action:
# <...>
on-error:
- error-type: "*"
handler: "_continue"
on-error:
- error-type: "AttemptsLimitReached"
handler: "notify"
error-handlers:
notify: # название обработчика
error-type-default: "*" # любой тип ошибки по умолчанию
apicall: "<...>" # команда, выполняющая запрос с синтаксисом sdc-cli
control-flow: "continue" # действие после обработки ошибки
Встроенные обработчики ошибок
Для всех процедур по умолчанию определены следующие обработчики ошибок
error-handlers:
_terminate:
control-flow: "terminate" # завершить процедуры с статусом ERROR
_exit:
control-flow: "exit" # завершить процедуру с статусом SUCCESS
_continue:
control-flow: "continue" # пропустить шаг и перейти к следующему
_repeat:
control-flow: "repeat" #повторить шаг еще раз
Логирование
В рамках любой команды можно добавить поле log, добавляющие сообщение в лог исполнения процедуры. log может быть beforeи after, то есть до и после исполнения команды. Если не указывать log явно, то по умолчанию используется after.
Сообщение лога — шаблон, поэтому в нем можно использовать любые доступные переменные. Если в команде используется распаковка, то по умолчанию log будет вызван только один раз: в начале и в конце всех команд. Если в сообщении используется распаковка, то log будет вызван для каждой отдельной команды.
Пример
vars:
somevar:
type: "object"
default: "{}"
steps:
step1:
conditions:
- apicall: "<...>"
log: "step1, condition 1, after: {{ somevar }}"
action:
apicall: "<...>"
parse:
- filter: "<...>"
op: "store"
var: "somevar"
log:
before: "step1, action, before: {{ somevar }}"
after: "step1, action, after: {{ somevar }}"
Результат
Конфигурация config
Поле config позволяет указать дополнительные настройки процедуры.
config:
run-on-node: <node_uuid> # Процедура будет исполняться на указанном узле
run-on-vip: <bool> # Процедура будет исполняться только на vip-узле
timeout: <int> # Максимальное время исполнения процедуры в секундах, по истечении которого процедура завершится
Расширения JMESPath
Стандартный набор функций jmespath, который используется для преобразования результатов выполнения команд, ограничен, поэтому для реализации более сложных процедур введены следующие расширения
union(array1, array2)
Объединение массивов
intersection(array1, array2)
Пересечение массивов (общие элементы)
difference(array1, array2)
Разность массивов (элементы из array1, которых нет в array2)
Дополнительная информация
- Управление процедурами.
- Примеры процедур описаны в следующей статье .