Обзор «сторожевых таймеров» менеджера высокой готовности и его дублера
Менеджер высокой готовности (HAM) ― отказоустойчивый механизм наблюдения за процессами и службами системы («умный сторож»), который способен поэтапно восстанавливать системные службы и процессы после отказов, сбоев и недопустимого ухудшения качества работы. HAM входит в состав комплекта высокой готовности, который информирует компоненты системы о важных событиях с помощью обычного механизма публикации и подписки. Комплект высокой готовности автоматически интегрируется в собственный сетевой механизм (Qnet), и наблюдает за сетью так же, как за локальной системой.
HAM играет роль коммуникатора, с помощью которого компоненты получают и публикуют информацию о состоянии системы — отдельного узла или нескольких узлов, связанных между собой через Qnet. Менеджер высокой готовности следит за конкретными процессами, а также управляет реакцией системы на сбои компонентов и действиями по их восстановлению. Кроме того, менеджер высокой готовности позволяет внешним детекторам информировать систему о важных событиях и задавать действия, которые выполняются при их наступлении.
Во многих системах высокой готовности необходимо локализовывать и защищать единые точки отказа (англ. Single Points Of Failure, SPOFs). Поскольку менеджер высокой готовности хранит информацию о состоянии системы и обеспечивает основные средства ее восстановления, он сам не должен становиться единой точкой отказа ни при каких условиях.
HAM следит за собственным состоянием и защищает себя от внутренних сбоев. В случае внештатной остановки менеджер высокой готовности немедленно и полностью восстанавливает свое прежнее состояние. Резервный процесс, который называется дублером, постоянно находится в режиме ожидания и принимает на себя управление при сбое менеджера высокой готовности. Поскольку вся информация хранится в общей памяти, дублер имеет доступ к состоянию, в котором HAM находился перед сбоем.
Перед тем, как заменить собой исходного менеджера высокой готовности, дублер создает для себя нового дублера, который активизируется при выходе нового менеджера высокой готовности из строя. Другими словами, менеджер высокой готовности и дублер всегда работают в паре, наблюдая друг за другом и восстанавливая своего «напарника» при сбое. Единственный способ остановить менеджера высокой готовности — дать ему команду завершить сначала дублера, а затем себя.
Менеджер высокой готовности состоит из трех основных компонентов:
Объекты — это основные элементы, за которыми осуществляется наблюдение в системе. Фактически каждый объект представляет собой процесс с уникальным идентификатором (pid) и символьным именем, по которому к нему можно обращаться. Имена объектов уникальны в пределах системы. Поскольку на текущий момент администратор связан с конкретным узлом, имена объектов должны быть уникальными в пределах узла. Как мы увидим позже, уникальные имена объектов очень похожи на имена файлов в иерархической файловой системе.
Существуют следующие типы объектов:
![]() | Чтобы получить дескриптор глобального объекта, следует вызвать функцию ham_entity_handle(), передав NULL в качестве аргумента ename. |
Как правило, для создания образов процессов, которые аварийно завершаются из-за выполнения недопустимых операций, используется утилита dumper. Она отправляет уведомления об авариях HAM, а система также информирует его о завершении любых процессов, относящихся к сеансу 1, в том числе демонов, которые отключились от своего управляющего терминала с помощью функции procmgr_daemon().
Если процесс вызывает функцию daemon(), создается новый процесс, который заменяет его и становится лидером сеанса. Если HAM ранее наблюдал за исходным процессом, он автоматически продолжает наблюдение за новым процессом.
Условия характеризуют состояние объектов. Ниже приведены примеры условий:
Менеджер высокой готовности автоматически обнаруживает и/или инициирует (публикует) вышеперечисленные условия, кроме CONDSTATE
, CONDRAISE
и CONDANY
. Условия CONDSTATE
и CONDRAISE
передаются HAM внешними детекторами. Подписчики могут назначать любому условию последовательность действий, которая выполняется при его возникновении. Условия CONDSTATE
и CONDRAISE
обеспечивают возможности фильтрации, которые позволяют подписчикам выборочно связывать действия с конкретными условиями в зависимости от опубликованной информации.
Условия также имеют символьные имена, уникальные в пределах объекта.
![]() | HAM обладает расширяемой архитектурой. Он самостоятельно обнаруживает ряд условий; также существует механизм инициации условий, который позволяет другим компонентам уведомлять HAM о важных событиях, происходящих в системе. При разработке отказоустойчивой системы можно можно гибко настраивать эти условия, а также изучать исходный код HAM и добавлять в него функции обнаружения других условий (например, нехватки оперативной памяти или дискового пространства, высокой нагрузки на ЦП и др.) |
Действия связаны с условиями: одно условие может включать в себя несколько действий, которые выполняются при его инициировании. Порядок выполнения действий соответствует порядку их добавления в условие (FIFO). Если несколько условий наступают одновременно, они инициируются в произвольном порядке. Условия типа HCONDINDEPENDENT
выполняются в отдельном потоке параллельно с другими условиями (см. раздел Функции для работы с условиями).
API менеджера высокой готовности включает в себя функции для выполнения различных действий:
Действие | Описание |
---|---|
ham_action_restart() | Перезапуск объекта. |
ham_action_execute() | Выполнение произвольной команды (например, запуск процесса). |
ham_action_notify_pulse() | Оповещение процесса о возникновении соответствующего условия в форме импульса, значение которого задается процессом-адресатом. Импульсы можно отправлять по сети, указывая спецификатор соответствующего удаленного узла. |
ham_action_notify_signal() | Оповещение процесса о возникновении соответствующего условия в форме сигнала реального времени, значение которого задается процессом-адресатом. Сигналы можно отправлять по сети, указывая спецификатор соответствующего удаленного узла. |
ham_action_notify_pulse_node() | Эта функция идентична описанной выше функции ham_action_notify_pulse(), однако в ней можно указывать полное имя узла вместо его идентификатора. |
ham_action_notify_signal_node() | Эта функция идентична описанной выше функции ham_action_notify_signal() однако в ней можно указывать полное имя узла вместо его идентификатора. |
ham_action_waitfor() | Добавление задержки между двумя действиями в последовательности. С помощью этой функции также можно ожидать создания определенных имен в пространстве имен. |
ham_action_heartbeat_healthy() | Сброс механизма контрольных сигналов для объекта, который перестал отправлять их и инициировал событие пропуска контрольных сигналов, но к настоящему моменту восстановил работоспособность. |
ham_action_log() | Регистрация настраиваемого информационного сообщения в журнале действий HAM. |
Действия также имеют символьные имена, уникальные в пределах условия.
![]() | Расширяемая архитектура HAM позволяет разработчику при необходимости создавать собственные функции действий. |
На случай, если при выполнении определенного действия последовательности происходит ошибка, можно задавать альтернативную последовательность действий для ее исправления. Альтернативное действие называется реакцией на ошибку выполнения действия, с которым оно связано. В качестве реакций на ошибки можно выполнять обычные действия, за исключением ham_action_restart() и ham_action_heartbeat_healthy(). Ниже перечислены функции реакций на ошибки:
Действие | Описание |
---|---|
ham_action_fail_execute() | Выполнение произвольной команды (например, запуск процесса). |
ham_action_fail_notify_pulse() | Оповещение процесса о возникновении соответствующего условия в форме импульса, значение которого задается процессом-адресатом. Импульсы можно отправлять по сети, указывая спецификатор соответствующего удаленного узла. |
ham_action_fail_notify_signal() | Оповещение процесса о возникновении соответствующего условия в форме сигнала реального времени, значение которого задается процессом-адресатом. Сигналы можно отправлять по сети, указывая спецификатор соответствующего удаленного узла. |
ham_action_fail_notify_pulse_node() | Эта функция идентична описанной выше функции ham_action_fail_notify_pulse(), однако в ней можно указывать полное имя узла вместо его идентификатора. |
ham_action_fail_notify_signal_node() | Эта функция идентична описанной выше функции ham_action_fail_notify_signal(), однако в ней можно указывать полное имя узла вместо его идентификатора. |
ham_action_fail_waitfor() | Добавление задержки между двумя действиями в последовательности. С помощью этой функции также можно ожидать создания определенных имен в пространстве имен. |
ham_action_fail_log() | Регистрация настраиваемого информационного сообщения в журнале действий менеджера высокой готовности. |
Этот механизм позволяет восстанавливать работоспособность службы или процесса в несколько стадий.
Допустим, что пользователь запустил файловую систему NFS ( fs-nfs3) и смонтировал несколько каталогов из различных источников. Можно дать HAM команду перезапускать процесс fs-nfs3 после сбоев и затем заново монтировать эти каталоги. Если во время работы fs-nfs3 какие-либо каталоги размонтируются, их повторное монтирование удаляется из списка действий.
Приведем еще один пример: если в менеджере сетевого ввода/вывода io-pkt-* произошел сбой, мы можем дать менеджеру высокой готовности команду перезапустить его, а затем загрузить необходимые сетевые драйверы и несколько дополнительных компонентов, которым сетевые службы необходимы для работы.
Состояние HAM похоже на иерархическую файловую систему, в которой объекты аналогичны каталогам, условия — подкаталогам, а действия — концевым узлам.
Менеджер высокой готовности публикует свое текущее состояние в виде файловой системы для чтения, которая позволяет произвольным процессам считывать его, например, с помощью команды:
ls /proc/ham
HAM публикует в файловой системе не только свое состояние, но и статистику и сведения о каждом ее элементе (объект/условие/действие) в файле .info
, который находится в соответствующем подкаталоге каталога /proc/ham
.
Рассмотрим простой пример, в котором HAM наблюдает за демоном inetd и перезапускает его после сбоев:
# ls -al /proc/ham total 2 -r-------- 1 root root 175 Aug 30 23:05 .info dr-x------ 1 root root 1 Aug 30 23:06 inetd
Файл .info
верхнего уровня содержит информацию о HAM, дублере, объектах и других компонентах системы:
# cat /proc/ham/.info Ham Pid : 10993674 Guardian Pid : 10997782 Ham Failures : 0 Guardian Failures : 0 Num Entities : 1 Num Conditions : 1 Num Actions : 1
Здесь inetd — единственный контролируемый объект; он представлен в виде подкаталога каталога /proc/ham
:
# ls -al /proc/ham/inetd total 2 -r-------- 1 root root 173 Aug 30 23:06 .info dr-x------ 1 root root 1 Aug 30 23:06 death # cat /proc/ham/inetd/.info Path : inetd Entity Pid : 11014167 Num conditions : 1 Entity type : ATTACHED Stats: Created : 2001/08/30 23:04:49:930148650 Num Restarts : 0
Как видно, файл .info
содержит информацию и статистику по объекту inetd. Эта информация генерируется динамически и включает в себя актуальные сведения о каждом объекте.
С объектом inetd связано единственное условие (death или завершение), которое возникает при завершении работы объекта.
# ls -al /proc/ham/inetd/death total 2 -r-------- 1 root root 126 Aug 30 23:07 .info -r-------- 1 root root 108 Aug 30 23:07 restart # cat /proc/ham/inetd/death/.info Path : inetd/death Entity Pid : 11014167 Num Actions : 1 Condition ReArm : ON Condition type : CONDDEATH
С этим условием связано единственное действие — restart (перезапуск). Каждое действие отображается в виде файла в каталоге соответствующего условия. Указанный файл включает в себя сведения о действии, которое выполняется при наступлении этого условия.
# cat /proc/ham/inetd/death/restart Path : inetd/death/restart Entity Pid : 11014167 Action ReArm : ON Restart Line : /usr/sbin/inetd -D
![]() | Если inetd не является самостоятельно присоединившимся объектом, следует передать ему ключ -D, чтобы «демонизировать» его посредством функции procmgr_daemon(), а не daemon(). Менеджер высокой готовности получает сообщения о завершении только тех объектов, которые самостоятельно присоединились к нему, завершились аварийно либо выполнялись в сеансе 1; функция daemon() не помещает вызывающий ее процесс в этот сеанс.
Если объект inetd присоединился самостоятельно, можно не передавать ему ключ -D, поскольку HAM начинает автоматически контролировать новый процесс, который создается функцией daemon(). |
Когда процесс inetd завершается, выполняются все действия, связанные с этим условием:
# slay inetd # cat /proc/ham/inetd/.info Path : inetd Entity Pid : 11071511 <- new pid of entity Num conditions : 1 Entity type : ATTACHED Stats: Created : 2001/08/30 23:04:49:930148650 Last Death : 2001/08/30 23:10:31:889820814 Restarted : 2001/08/30 23:10:31:904818519 Num Restarts : 1
Как видно, статистика по объекту inetd обновилась.
Аналогично, если завершается сам HAM, его место занимает дублер, который создает для себя нового дублера.
# cat /proc/ham/.info Ham Pid : 10993674 <----- менеджер высокой готовности Guardian Pid : 10997782 <----- дублер (Guardian) Ham Failures : 0 Guardian Failures : 0 Num Entities : 1 Num Conditions : 1 Num Actions : 1 ... Уничтожение менеджера высокой готовности .... # /bin/kill -9 10993674 <---- имитация ошибки ... повторное считывание статистики ... # cat /proc/ham/.info Ham Pid : 10997782 <----- новый менеджер высокой готовности Guardian Pid : 11124746 <----- дублер (Guardian) Ham Failures : 1 Guardian Failures : 0 Num Entities : 1 Num Conditions : 1 Num Actions : 1
Как видно, прежний дублер заменил собой HAM и создал нового дублера (Guardian). Все объекты и условия сохранились, а наблюдение продолжилось в обычном режиме. HAM и дублер игнорируют все сигналы, которые допустимо игнорировать.
HAM предоставляет API с функциями для взаимодействия с ним и выполнения следующих действий:
API реализован в виде библиотеки, которую можно привязывать к программам и использовать в многопоточной среде с механизмом отмены потоков.
Библиотека API обеспечивает только одно подключение к HAM. Она работает в многопоточной среде и объединяет множество подключений одного или нескольких потоков в единое подключение к HAM с использованием счетчика ссылок.
Ниже перечислены основные функции подключения к менеджеру высокой готовности:
/* Основные функции подключения возвращают признак успешного выполнения (0) или ошибки (-1 и переменную errno) */int ham_connect( unsigned flags );int ham_connect_nd( int nd,unsigned flags );int ham_connect_node( const char *nodename,unsigned flags );int ham_disconnect( unsigned flags );int ham_disconnect_nd( int nd,unsigned flags );int ham_disconnect_node( const char *nodename,unsigned flags );
Эти функции открывают и закрывают подключения к HAM. При первом вызове функция ham_connect*() открывает файловый дескриптор fd, а при последующих вызовах инкрементирует счетчик ссылок.
Аналогичным образом функция ham_disconnect() декрементирует счетчик ссылок до нуля, а вызов, обнуляющий счетчик ссылок, закрывает файловый дескриптор fd. Эти функции возвращают -1
при возникновении ошибки и 0
при успешном выполнении. Аналогично функция ham_disconnect*() декрементирует счетчик ссылок до нуля, а вызов, обнуляющий счетчик ссылок, закрывает файловый дескриптор. Эти функции возвращают -1
(и присваивают значение переменной errno) при возникновении ошибки и 0
при успешном выполнении.
В многопоточной среде в каждый момент времени открыто только одно подключение к HAM, даже если функции ham_connect*() / ham_disconnect*() выполняются несколькими потоками.
Функции ham_*_nd() и ham_*_node() открывают подключение к удаленному HAM по протоколу Qnet. В параметре nd указывается идентификатор удаленного узла в момент вызова функции. Поскольку идентификаторы узлов могут изменяться с течением времени, необходимо запрашивать идентификатор нужного узла непосредственно перед вызовом функции. В качестве альтернативы можно указывать полное имя узла (англ. Fully Qualified Node Name, FQNN) в параметре nodename. Значение ND_LOCAL_NODE
параметра nd (константа, определенная в файле <sys/netmgr.h>
) и значение NULL
параметра nodename (либо пустая строка) эквивалентны друг другу и указывают на текущий узел (их применение аналогично непосредственному вызову функции ham_connect() или ham_disconnect()).
Вызовы функций ham_connect(), ham_connect_nd() и ham_connect_node() можно чередовать друг с другом; необходимо лишь следить за тем, чтобы количество вызовов функций подключения совпадало с количеством вызовов функций отключения для каждого конкретного (локального или сетевого) соединения с HAM (файлового дескриптора fd) перед его закрытием.
Самостоятельное присоединение объектов
ham_entity_t * ham_attach_self( char *ename,uint64_t hp,int hpdl,int hpdh,unsigned flags );int ham_detach_self( ham_entity_t *ehdl,unsigned flags );
Эти две функции используются для самостоятельного присоединения и отсоединения объекта от менеджера высокой готовности.
Аргумент ename содержит символьное имя объекта, которое должно быть уникальным в пределах системы (среди всех контролируемых объектов в момент вызова функции).
Аргумент hp содержит длительность периода отправки контрольных сигналов в наносекундах. Передача контрольных сигналов применяется для наблюдения за работоспособностью объекта. Работоспособность — это состояние объекта, в котором он выполняет возложенные на него задачи. Нарушение готовности компонента часто проявляется не в его завершении, а в отсутствии реакции на запросы или прекращении выполнения каких-либо действий. Можно настроить отправку компонентом контрольных сигналов через заданные промежутки времени; пропуск указанного количества отправок инициирует условие отсутствия контрольных сигналов.
Аргументы hpdl и hpdh содержат количество пропущенных контрольных сигналов, при которых возникают условия heartbeatmissedlow и heartbeatmissedhigh. Библиотека API регистрирует этот запрос и создает поток, который администрирует подключение к HAM. При аварийном завершении объекта это подключение закрывается и HAM обнаруживает сбой, поскольку перед завершением объект не вызвал функцию ham_detach_self().
Если HAM аварийно завершается (что крайне маловероятно) и его место занимает дублер, подключение к прежнему HAM становится недействительным; дублер отправляет всем самостоятельно присоединившимся объектам запрос на повторное присоединение, а упомянутый выше дополнительный поток выполняет его в прозрачном режиме.
Если подключение к HAM уже открыто, функция ham_attach_self() использует его и инкрементирует счетчик открытых клиентом подключений. Если клиент объявил о намерении отправлять HAM контрольные сигналы с заданным периодом, он обязан делать это с помощью функции ham_heartbeat().
Библиотека также проверяет уникальность имени, указанного в аргументе ename. Если такого имени не существует, запрос передается HAM, который также проверяет его во избежание конфликтов при создании новых объектов. Функция ham_attach_self() возвращает обычный дескриптор, который является непрозрачным указателем и может использоваться процессом для последующего отсоединения от HAM, а также добавления условий и действий, как показано далее.
Функция ham_detach_self() закрывает подключение к HAM, который прекращает наблюдение за самостоятельно подключившимся объектом и отменяет дополнительный поток. Функция ham_detach_self() принимает в качестве аргумента дескриптор, возвращаемый функцией ham_attach_self().
Фрагмент кода с вызовами функций самостоятельного присоединения/отсоединения
В следующем фрагменте кода используются функции ham_attach() | detach_self():
...ham_entity_t *ehdl; /* дескриптор объекта */int status;/* подключение к менеджеру высокой готовности с периодом контрольного сигнала 5 секунд, именем* объекта "client1", без использования флагов. Также указаны параметры hpdh = 4 и hpdh = 8 */ehdl = ham_attach_self( "client1", 5000000000, 4, 8, 0 );if ( ehdl == NULL ){printf( "Could not attach to Ham\n" );exit( -1 );}/* Отсоединение от менеджера высокой готовности с помощью исходного дескриптора */status = ham_detach_self( ehdl, 0 );...
Присоединение/отсоединение любых других объектов
ham_entity_t * ham_attach( char *ename,int nd,pid_t pid,char *line,unsigned flags );ham_entity_t * ham_attach_node( char *ename,const char *nodename,pid_t pid,char *line,unsigned flags );int ham_detach( ham_entity_t *ehdl,unsigned flags );int ham_detach_name( int nd,char *ename,unsigned flags );int ham_detach_name_node( const char *nodename,char *ename,unsigned flags );
Функции attach/detach/detach-name очень похожи на рассмотренные ранее функции *_self(), но дают менеджеру высокой готовности команду контролировать другой процесс.
Этот механизм позволяет наблюдать за любыми существующими объектами, которые не скомпилированы с библиотекой API HAM, при этом наблюдение может выполняться без их ведома.
Вызов функции ham_attach() позволяет:
Если мы предполагаем, что объект не выполняется, мы указываем -1
в качестве параметра pid в вызове ham_attach(). В этом случае объект запускается с помощью команды, указанной в строке line. Если значение pid больше 0
, команда line игнорируется и менеджер высокой готовности присоединяет объект с указанным pid. Значение параметра ename должно быть уникальным среди объектов, зарегистрированных в момент вызова функции.
Спецификатор nd функций ham_attach() и ham_detach_name() и спецификатор nodename функций ham_attach_node() и ham_detach_name_node() используются для обращения к удаленному HAM через протокол Qnet. В параметре nd указывается идентификатор удаленного узла в момент вызова функции. Поскольку идентификаторы узлов могут изменяться со временем, необходимо запрашивать идентификатор нужного узла непосредственно перед вызовом функции. В качестве альтернативы можно указывать полное имя узла в параметре nodename. Значение ND_LOCAL_NODE
параметра nd (константа, определенная в файле <sys/netmgr.h>
) и значение NULL
параметра nodename (либо пустая строка) эквивалентны друг другу и указывают на текущий узел.
Функции ham_detach*() прекращают наблюдение за конкретным объектом. Вызов функции ham_detach() принимает в качестве аргумента исходный дескриптор, возвращаемый функцией ham_attach(). В функции ham_detach_name() вместо дескриптора используется имя объекта.
С помощью дескриптора также можно добавлять условия в объект (см. далее).
Фрагмент кода с вызовами функций присоединения/отсоединения других объектов
...ham_entity_t *ehdl;int status;ehdl = ham_attach( "inetd", 0, -1, "/usr/sbin/inetd -D", 0 );/* процесс inetd запущен и работает под наблюдением */...status = ham_detach( ehdl, 0 );...
Разумеется, присоединение и отсоединение от менеджера высокой готовности не обязательно выполнять в одном потоке:
...ham_entity_t *ehdl;int status;/* запуск процесса inetd и наблюдения за ним */ehdl = ham_attach( "inetd", 0, -1, "/usr/sbin/inetd -D", 0 );.../* отсоединение от менеджера высокой готовности с продолжением наблюдения */exit( 0 );
Отсоединение inetd:
...int status;/* прекращение наблюдения за inetd. */status = ham_detach_name( 0, "inetd", 0 );...exit( 0 );
Если бы процесс inetd уже выполнялся (например, с pid 105328676), код присоединения/отсоединения было бы можно написать следующим образом:
ham_entity_t *ehdl;int status;ehdl = ham_attach( "inetd", 0, 105328676, NULL, 0 );...status = ham_detach( ehdl, 0 );/* status = ham_detach_name(0, "inetd",0); */...exit( 0 );
Функции ham_attach() и ham_detach() создают подключение к HAM, если оно не было создано ранее; это делается только для того, чтобы облегчить их использование.
Подключения к HAM существуют только между вызовами функций присоединения и отсоединения; перед любыми последующими обращениями к HAM необходимо вызывать функцию ham_connect().
При отправке большого количества последовательных запросов HAM рекомендуется:
Этот метод наиболее эффективен, поскольку гарантирует, что все запросы отправляются через одно и то же подключение к HAM.
Функции ham_attach_*() обычно используются, если объект уже выполняется либо будет запущен HAM; наблюдение начинается с момента их вызова. API менеджера высокой готовности также включает в себя две функции для создания шаблонов объектов, которые еще не выполняются, но могут быть запущены позднее. Этот механизм позволяет процессам подписываться на интересующие события, не дожидаясь, когда объект будет создан публикатором (другим объектом или HAM).
ham_entity_t * ham_entity( const char *ename,int nd,unsigned flags );ham_entity_t * ham_entity_node( const char *ename,const char *nodename,unsigned flags );
Эти функции создают шаблоны объектов с именем ename на узле с идентификатором nd или именем nodename. В созданные шаблоны можно добавлять условия и действия для соответствующих объектов. При последующем вызове функции ham_attach*() с именем шаблона в параметре ename выполняется поиск шаблона с соответствующим идентификатором процесса, после чего начинается обычное наблюдение за объектом.
ham_condition_t * ham_condition( ham_entity_t *ehdl,int type,const char *cname,unsigned flags );int ham_condition_remove( ham_condition_t *chdl,unsigned flags );
С каждым объектом можно связывать различные условия, а с каждым условием — набор действий, которые последовательно выполняются при его возникновении. Если одновременно возникают несколько условий объекта, с которыми связаны различные наборы действий, каждый такой набор выполняется последовательно.
Этот механизм позволяет объединять действия в группы для последующего удаления и управления.
Поскольку условия связаны с объектами, для добавления условий необходимо получать дескриптор объекта. Функции ham_condition*() возвращают дескриптор условия — непрозрачный указатель, который можно использовать для добавления и удаления действий из условия.
Можно указывать следующие типы условий:
Условия CONDATTACH
, CONDDETACH
и CONDRESTART
инициируются HAM при присоединении, отсоединении и перезапуске объектов соответственно. Условия CONDHBEATMISSEDHIGH
и CONDHBEATMISSEDLOW
инициируются HAM, когда он обнаруживает пропущенные контрольные сигналы от объектов, уведомивших его о намерении отправлять их.
Условие CONDDEATH
инициируется при завершении объекта. Условие CONDABNORMALDEATH
инициируется только при аварийном завершении объекта, однак одновременно с ним также инициируется условие CONDDEATH
.
Условие detach (отсоединение) позволяет выполнять действия при корректном отсоединении наблюдаемого объекта от HAM. После отсоединения наблюдение за объектом прекращается. С помощью этого условия можно уведомлять клиентов о том, что HAM больше не публикует информацию об отсоединившемся объекте.
Условие restart (перезапуск) автоматически создается и инициируется HAM при завершении и перезапуске объекта.
HCONDNOWAIT
обрабатываются в отдельном потоке и не приостанавливаются действиями, выполняемыми в других потоках. Если у условия установлены флаги HCONDINDEPENDENT
и HCONDNOWAIT
, флаг HCONDNOWAIT
имеет приоритет; все действия этого условия выполняются в одном потоке с действиями всех остальных условий с флагом HCONDNOWAIT
, поскольку этим условиям уже гарантированы минимальные задержки.
Условие, у которого не установлен ни один из флагов HCONDNOWAIT
и HCONDINDEPENDENT
, обрабатывается вместе с другими условиями (HCONDOTHER
) в порядке наступления (FIFO).
Резюмируем:
CONDDEATH
, CONDDETACH
и др.) с флагом HCONDNOWAIT
выполняются в одном потоке в порядке наступления (FIFO).
HCONDINDEPENDENT
(но без флага HCONDNOWAIT
) выполняется в отдельном потоке.
Таким образом, максимальное количество потоков обработки условий составляет:
(количество условий HCONDINDEPENDENT
) + 2,
где один поток обрабатывает все условия с флагом HCONDNOWAIT
, а другой поток — все остальные условия.
Все действия конкретного условия выполняются в порядке очереди (FIFO) независимо от состояния флагов HCONDNOWAIT
и HCONDINDEPENDENT
.
/* операции действий */ham_action_t * ham_action_restart( ham_condition_t *chdl,const char *aname,const char *path,unsigned flags );ham_action_t * ham_action_execute( ham_condition_t *chdl,const char *aname,const char *path,unsigned flags );ham_action_t * ham_action_waitfor( ham_condition_t *chdl,const char *aname,const char *path,int delay,unsigned flags );ham_action_t * ham_action_notify_pulse( ham_condition_t *chdl,const char *aname,int nd,int topid,int chid,int pulsecode,int value,unsigned flags );ham_action_t * ham_action_notify_signal( ham_condition_t *chdl,const char *aname,int nd,pid_t topid,int signum,int code,int value,unsigned flags );ham_action_t * ham_action_notify_pulse_node( ham_condition_t *chdl,const char *aname,const char *nodename,int topid,int chid,int pulsecode,int value,unsigned flags );ham_action_t * ham_action_notify_signal_node( ham_condition_t *chdl,const char *aname,const char *nodename,pid_t topid,int signum,int code,int value,unsigned flags );ham_action_t * ham_action_heartbeat_healthy( ham_condition_t *chdl,const char *aname,unsigned flags );ham_action_t * ham_action_log( ham_condition_t *chdl,const char *aname,const char *msg,unsigned attachprefix,int verbosity,unsigned flags );/* удаление действия */int ham_action_remove( ham_action_t *ahdl,unsigned flags );
Как было отмечено ранее, на сегодняшний день HAM поддерживает ряд типовых функций действий, однако разработчик может создавать собственные функции действий для конкретных систем высокой готовности.
![]() | Действия restart можно связывать только с условиями death, каждое из которых может содержать только одно действие restart в каждый момент времени. Это гарантирует, что объект перезапускается однократно и только после завершения (к типу death относятся условия CONDDEATH в CONDABNORMALDEATH ). |
HACTIONDONOW
выполняется немедленно. Этот механизм также удобно использовать для поэтапного создания и запуска объекта. HACTIONDONOW
игнорируется для действий waitfor, поэтому для добавления задержек в последовательность действий с флагом HACTIONDONOW
необходимо вставлять их в клиентскую программу между вызовами ham_action*(). NULL
), функция выполняет задержку на delay мс, а если задан — ожидает delay мс или создания имени path в пространстве имен (в зависимости от того, что произойдет раньше). Следует иметь в виду, что если путевое имя указан, длительность задержки равна ближайшему кратному 100 мс с округлением в большую сторону. При задержке, равной 0, действие waitfor фактически не выполняется, а аргумент pathname игнорируется. HREARMAFTERRESTART
в аргумент флагов вызова ham_condition() или соответствующего действия с помощью операции ИЛИ. ACTIONRESTART
(оно не используется или при его выполнении произошла ошибка), объект удаляется вместе со всеми условиями и действиями. /* Операции реагирования на ошибки выполнения действий */int ham_action_fail_execute( ham_action_t *ahdl,const char *aname,const char *path,unsigned flags );int ham_action_fail_waitfor( ham_action_t *ahdl,const char *aname,const char *path,int delay,unsigned flags );int ham_action_fail_notify_pulse( ham_action_t *ahdl,const char *aname,int nd,int topid,int chid,int pulsecode,int value,unsigned flags );int ham_action_fail_notify_signal( ham_action_t *ahdl,const char *aname,int nd,pid_t topid,int signum,int code,int value,unsigned flags );int ham_action_fail_notify_pulse_node( ham_action_t *ahdl,const char *aname,const char *nodename,int topid,int chid,int pulsecode,int value,unsigned flags );int ham_action_fail_notify_signal_node( ham_action_t *ahdl,const char *aname,const char *nodename,pid_t topid,int signum,int code,int value,unsigned flags );int ham_action_fail_log( ham_action_t *ahdl,const char *aname,const char *message,unsigned attachprefix,int verbosity,unsigned flags );/* удаление операции реагирования на ошибку выполнения действия */int ham_action_fail_remove( ham_action_t *ahdl,const char *aname,unsigned flags );
Эти функции выполняются при возникновении ошибок в действиях, которые связаны с условием. Они аналогичны функциям самих действий, описанным в предыдущем разделе, за исключением первого параметра, которым является дескриптор действия, а не условия.
Пример наблюдения за процессом inetd
В следующем фрагменте кода показан запуск наблюдения за процессом inetd:
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <unistd.h>#include <sys/stat.h>#include <sys/netmgr.h>#include <fcntl.h>#include <ha/ham.h>int main( int argc, char *argv[] ){int status;char *inetdpath;ham_entity_t *ehdl;ham_condition_t *chdl;ham_action_t *ahdl;int inetdpid;inetdpath = strdup( "/usr/sbin/inetd -D" );inetdpid = -1;ham_connect( 0 );ehdl = ham_attach( "inetd", ND_LOCAL_NODE, inetdpid, inetdpath, 0 );if ( ehdl != NULL ){chdl = ham_condition( ehdl, CONDDEATH, "death", HREARMAFTERRESTART );if ( chdl != NULL ){ahdl = ham_action_restart( chdl, "restart", inetdpath, HREARMAFTERRESTART );if ( ahdl == NULL )printf("add action failed\n");} elseprintf( "add condition failed\n" );} elseprintf( "add entity failed\n" );ham_disconnect( 0 );exit( 0 );}
Пример наблюдения за процессом fs-nfs3
В следующем фрагменте кода показан запуск наблюдения за процессом fs-nfs3:
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <unistd.h>#include <sys/stat.h>#include <sys/netmgr.h>#include <fcntl.h>#include <ha/ham.h>int main( int argc, char *argv[] ){int status;ham_entity_t *ehdl;ham_condition_t *chdl;ham_action_t *ahdl;char *fsnfspath;int fsnfs3pid;fsnfspath = strdup( "/usr/sbin/fs-nfs3" );fsnfs3pid = -1;ham_connect( 0 );ehdl = ham_attach( "Fs-nfs3", ND_LOCAL_NODE, fsnfs3pid, fsnfspath, 0 );if ( ehdl != NULL ){chdl = ham_condition( ehdl, CONDDEATH, "Death", HREARMAFTERRESTART );if ( chdl != NULL ){ahdl = ham_action_restart( chdl, "Restart", fsnfspath, HREARMAFTERRESTART );if ( ahdl == NULL )printf("add action failed\n");else {ahdl = ham_action_waitfor( chdl, "Delay1", NULL, 2000, HREARMAFTERRESTART );if ( ahdl == NULL )printf( "add action failed\n" );ahdl = ham_action_execute( chdl, "MountDir1", "/bin/mount -t nfs a.b.c.d:/dir1 /dir1",HREARMAFTERRESTART | HACTIONDONOW );if ( ahdl == NULL )printf( "add action failed\n" );ahdl = ham_action_waitfor( chdl, "Delay2", NULL, 2000, HREARMAFTERRESTART );if ( ahdl == NULL )printf( "add action failed\n" );ahdl = ham_action_execute( chdl, "Mountdir2", "/bin/mount -t nfs a.b.c.d:/dir2 /dir2",HREARMAFTERRESTART | HACTIONDONOW );if ( ahdl == NULL )printf( "add action failed\n" );}} elseprintf( "add condition failed\n" );} elseprintf( "add entity failed\n" );ham_disconnect( 0 );exit( 0 );}
/* получение/освобождение дескрипторов */ham_entity_t * ham_entity_handle( int nd,const char *ename,unsigned flags );ham_condition_t * ham_condition_handle( int nd,const char *ename,const char *cname,unsigned flags );ham_action_t * ham_action_handle( int nd,const char *ename,const char *cname,const char *aname,unsigned flags );ham_entity_t * ham_entity_handle_node( const char *nodename,const char *ename,unsigned flags );ham_condition_t * ham_condition_handle_node( const char * nodename,const char *ename,const char *cname,unsigned flags );ham_action_t * ham_action_handle_node( const char * nodename,const char *ename,const char *cname,const char *aname,unsigned flags );int ham_entity_handle_free( ham_entity_t *ehdl );int ham_condition_handle_free( ham_condition_t *chdl );int ham_action_handle_free( ham_action_t *ahdl );
Функции для работы с дескрипторами позволяют получать и освобождать дескрипторы по имени объекта, условия и действия. С помощью полученных дескрипторов можно добавлять и удалять условия и действия. Аналогично другим функциям, у функций для работы с дескрипторами существуют версии типа *_node(), которые позволяют обращаться к HAM по сети с указанием полного имени узла (FQNN).
Ниже приведен пример клиента, который получает от HAM уведомления о важных событиях в виде импульсов и сигналов. Клиент регистрирует механизм уведомления о завершении или отсоединении процесса inetd в виде импульса и завершении процесса fs-nfs3 в виде сигнала.
В этом примере также показана проблема задержки уведомлений и ее решение с помощью условия HCONDINDEPENDENT
.
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <unistd.h>#include <sys/stat.h>#include <fcntl.h>#include <errno.h>#include <sys/neutrino.h>#include <sys/iomsg.h>#include <sys/netmgr.h>#include <signal.h>#include <ha/ham.h>#define PCODEINETDDEATH (_PULSE_CODE_MINAVAIL + 1)#define PCODEINETDDETACH (_PULSE_CODE_MINAVAIL + 2)#define PCODENFSDELAYED (_PULSE_CODE_MINAVAIL + 3)#define PCODEINETDRESTART1 (_PULSE_CODE_MINAVAIL + 4)#define PCODEINETDRESTART2 (_PULSE_CODE_MINAVAIL + 5)#define MYSIG (SIGRTMIN + 1)int fsnfs_value;/* Обработчик сигналов для обработки уведомлений о завершении процесса fs-nfs3 */void MySigHandler( int signo, siginfo_t *info, void *extra ){printf( "Received signal %d, with code = %d, value %d\n", signo, info->si_code, info->si_value.sival_int );if ( info->si_value.sival_int == fsnfs_value )printf( "FS-nfs3 died, this is the notify signal\n" );return;}int main( int argc, char *argv[] ){int chid, coid, rcvid;struct _pulse pulse;pid_t pid;int status, value;ham_entity_t *ehdl;ham_condition_t *chdl;ham_action_t *ahdl;struct sigaction sa;int scode, svalue;/* для приема импульсов необходимо создать канал */chid = ChannelCreate( 0 );/* для доставки импульсов необходимо подключиться к этому каналу */coid = ConnectAttach( 0, 0, chid, _NTO_SIDE_CHANNEL, 0 );/* заполнение структуры события для импульса */pid = getpid();value = 13;ham_connect( 0 );/* считаем, что объект с именем "inetd" уже существует */chdl = ham_condition_handle( ND_LOCAL_NODE, "inetd","death", 0 );ahdl = ham_action_notify_pulse( chdl, "notifypulsedeath", ND_LOCAL_NODE, pid, chid,PCODEINETDDEATH, value, HREARMAFTERRESTART );ham_action_handle_free( ahdl );ham_condition_handle_free( chdl );ehdl = ham_entity_handle( ND_LOCAL_NODE, "inetd", 0 );chdl = ham_condition( ehdl, CONDDETACH, "detach", HREARMAFTERRESTART );ahdl = ham_action_notify_pulse( chdl, "notifypulsedetach", ND_LOCAL_NODE, pid, chid,PCODEINETDDETACH, value, HREARMAFTERRESTART );ham_action_handle_free( ahdl );ham_condition_handle_free( chdl );ham_entity_handle_free( ehdl );fsnfs_value = 18; /* значение, которое будет использоваться как признак завершения fs-nfs */scode = 0;svalue = fsnfs_value;sa.sa_sigaction = MySigHandler;sigemptyset( &sa.sa_mask );sa.sa_flags = SA_SIGINFO;sigaction( MYSIG, &sa, NULL );/* Считаем, что объект с именем "Fs-nfs3" уже существует. Имя "Fs-nfs3" используется в качестве* символьного идентификатора объекта fs-nfs3. Имя объекта может быть любым, однако рекомендуется* использовать легкочитаемые и осмысленные имена. */ehdl = ham_entity_handle( ND_LOCAL_NODE, "Fs-nfs3", 0 );/* Добавление нового "независимого" условия. Его уведомления/действия защищены от задержек "waitfor"* в потоках, которые выполняют другие последовательности действий */chdl = ham_condition( ehdl, CONDDEATH, "DeathSep", HCONDINDEPENDENT | HREARMAFTERRESTART );ahdl = ham_action_notify_signal( chdl, "notifysignaldeath", ND_LOCAL_NODE, pid, MYSIG, scode,svalue, HREARMAFTERRESTART );ham_action_handle_free( ahdl );ham_condition_handle_free( chdl );ham_entity_handle_free( ehdl );chdl = ham_condition_handle( ND_LOCAL_NODE, "Fs-nfs3","Death",0 );/* Это действие добавляется в условие без флага HCONDNOWAIT. Условие может включать в себя* последовательность действий с произвольными задержками и ожиданиями, которые препятствуют* доставке уведомлений. */ahdl = ham_action_notify_pulse( chdl, "delayednfsdeathpulse", ND_LOCAL_NODE, pid, chid,PCODENFSDELAYED, value, HREARMAFTERRESTART );ham_action_handle_free( ahdl );ham_condition_handle_free( chdl );ehdl = ham_entity_handle( ND_LOCAL_NODE, "inetd", 0 );/* Мы указываем, что это условие является независимым */chdl = ham_condition( ehdl, CONDRESTART, "restart", HREARMAFTERRESTART | HCONDINDEPENDENT );ahdl = ham_action_notify_pulse( chdl, "notifyrestart_imm", ND_LOCAL_NODE, pid, chid,PCODEINETDRESTART1, value, HREARMAFTERRESTART );ham_action_handle_free( ahdl );ahdl = ham_action_waitfor( chdl, "delay", NULL, 6532, HREARMAFTERRESTART );ham_action_handle_free( ahdl );ahdl = ham_action_notify_pulse( chdl, "notifyrestart_delayed", ND_LOCAL_NODE, pid, chid,PCODEINETDRESTART2, value, HREARMAFTERRESTART );ham_action_handle_free( ahdl );ham_condition_handle_free( chdl );ham_entity_handle_free( ehdl );while ( 1 ){rcvid = MsgReceivePulse( chid, &pulse, sizeof( pulse ), NULL );if ( rcvid < 0 ){if ( errno != EINTR )exit( -1 );} else {switch ( pulse.code ){case PCODEINETDDEATH:printf( "Inetd Death Pulse\n" );break;case PCODENFSDELAYED:printf( "Fs-nfs3 died: this is the possibly delayed pulse\n" );break;case PCODEINETDDETACH:printf( "Inetd detached, so quitting\n" );goto the_end;case PCODEINETDRESTART1:printf( "Inetd Restart Pulse: Immediate\n" );break;case PCODEINETDRESTART2:printf( "Inetd Restart Pulse: Delayed\n" );break;}}}/* Мы больше не ждем информацию о процессе inetd, поскольку знаем, что он завершен. Мы* продолжаем получать информацию о завершении процесса fs-nfs3, поскольку не удалили эти* действия. Если мы завершим работу сейчас, при следующем выполнении этих действий произойдет* ошибка (поскольку получателя уведомлений больше не существует), после чего они будут* автоматически удалены и уничтожены. */the_end:ham_disconnect( 0 );exit( 0 );}
![]() | Следует иметь в виду, что в API HAM существует ряд ограничений:
|
HAM запускается командой ham в командной строке:
ham
Утилита ham имеет следующие параметры:
При запуске HAM создает своего дублера.
![]() | Необходимо указывать полный путь к утилите ham в команде запуска менеджера высокой готовности или включать его в переменную PATH.
Только пользователь |
Чтобы остановить HAM, необходимо воспользоваться функцией ham_stop() или утилитой hamctrl. Других корректных и гарантированных способов остановки HAM не существует.
Функция ham_stop() и утилита hamctrl дают HAM команду завершить работу. HAM сначала завершает дублера, а затем завершается сам. Чтобы остановить HAM с помощью командной строки, следует ввести команду:
hamctrl -stop
Для остановки удаленного HAM применяется ключ -node:
hamctrl -node "nodename" -stop
Для остановки HAM в программе с помощью API применяются следующие функции:
/* завершение работы */int ham_stop( void );int ham_stop_nd( int nd );int ham_stop_node( const char *nodename );
Следующие функции позволяют управлять настройками объектов, условий и действий.
/* Операции управления */int ham_entity_control( ham_entity_t *ehdl,int command,unsigned flags );int ham_condition_control( ham_condition_t *chdl,int command,unsigned flags );int ham_action_control( ham_action_t *ahdl,int command,unsigned flags );
Существуют следующие операции (команды):
Функции «включения» и «отключения» временно отображают/скрывают объект, условие или действие.
Скрытый объект не удаляется, но наблюдение за его условиями не осуществляется. Аналогично, скрытые условия не иниицируются, а скрытые действия не выполняются. По умолчанию операции включения и отключения не рекурсивны, хотя условия отключенного объекта не инициируются, а действия отключенного условия не выполняются.
Тонкие различия между рекурсивным выполнением операций управления см. в описаниях следующих функций API:
С помощью команд addflags, removeflags, setflags и getflags можно считывать и изменять флаги объектов, условий и действий. Дополнительную информацию см. в описании функций ham_*_control_*().
Функция ham_verbose() позволяет программно считывать и задавать (уменьшать или увеличивать) уровень детализации информации:
int ham_verbose( const char *nodename,int op,int value );
Для интерактивного управления детализацией информации можно использовать утилиту hamctrl:
hamctrl -verbose /* increase verbosity */ hamctrl +verbose /* decrease verbosity */ hamctrl =verbose /* get current verbosity */
Для работы с удаленным HAM следует использовать утилиту hamctrl с ключом -node:
hamctrl -node "nodename" -verbose /* increase verbosity */ hamctrl -node "nodename" +verbose /* decrease verbosity */ hamctrl -node "nodename" =verbose /* get current verbosity */
где nodename — действующее имя удаленного или локального узла.
Объекты и другие компоненты системы могут публиковать в HAM условия, при наступлении которых он отправляет уведомления компонентам-подписчикам. Этот механизм позволяет произвольным компонентам системы передавать информацию о самостоятельно обнаруживаемых ошибках и рисках их возникновения HAM, который, в свою очередь, передает ее другим компонентам для выполнения профилактических или корректирующих процедур.
На текущий момент существуют два принципиальных метода публикации данных в HAM, на основе которых можно создавать более сложные механизмы обмена информацией.
Объект может передавать информацию об изменениях своего состояния HAM, который сохраняет ее. HAM не интерпретирует состояния и не проверяет корректность их изменения, но может генерировать события при переходе из одного состояния в другое.
Одни компоненты могут публиковать изменения, которые представляют интерес для других. Публикуемые состояния не обязательно соответствуют фактическим состояниям, на основе которых приложение принимает решения.
Для публикации изменения состояния можно использовать указанную ниже функцию. Поскольку HAM интересует только новое состояние объекта, он получает информацию только о нем. Затем HAM инициирует событие изменения состояния, на которое могут подписываться другие компоненты системы при помощи функции ham_condition_state(), описанной далее.
/* публикация изменения состояния */int ham_entity_condition_state( ham_entity_t *ehdl,unsigned tostate,unsigned flags );
Компоненты системы также могут публиковать условия, которые они обнаруживают самостоятельно, с помощью функции API HAM ham_entity_condition_raise(). Компонент, который публикует условие, может указывать его тип, класс и важность, что позволяет другим компонентам гибко фильтровать условия, на которые они подписываются. В результате этого вызова HAM генерирует событие инициации условия, на которое другие компоненты могут подписываться с помощью функции ham_condition_raise(), описанной далее.
/* публикация самостоятельно обнаруживаемых условий */int ham_entity_condition_raise( ham_entity_t *ehdl,unsigned rtype,unsigned rclass,unsigned severity,unsigned flags );
Компоненты могут подписываться на события, которые публикуют другие компоненты, с помощью следующих функций API менеджера высокой готовности:
Эти функции аналогичны ham_condition() и возвращают дескриптор условия, однако дают подписчику возможность выбирать из множества опубликованных условий.
Когда объект публикует изменение своего состояния, инициируется условие, которое зависит от прежнего и нового состояний. Подписчики указывают интересующие состояния в параметрах fromstate (исходное состояние) и tostate (новое состояние) функции API HAM.
Более подробную информацию см. в описании функции ham_condition_state().
ham_condition_t * ham_condition_state( ham_entity_t *ehdl,const char *cname,unsigned fromstate,unsigned tostate,unsigned flags );
Подписчики могут реагировать на конкретные условия, инициируемые объектами, указывая их с помощью параметров функции ham_condition_raise().
Более подробную информацию см. в описании функции ham_condition_raise().
ham_condition_t * ham_condition_raise( ham_entity_t *ehdl,const char *cname,unsigned rtype,unsigned rclass,unsigned rseverity,unsigned flags);
Предыдущий раздел: Менеджер высокой готовности (HAM)