вторник, 30 июня 2009 г.

SMF и Method "stop" exited with status 208

Хотя при детальном изучении мануалов и блюпринтов по SMF (кстати, в подробном виде появившихся сравнительно недавно) все кажется сравнительно простым, на самом деле многие моменты далеко не очевидны.

При разработке сервисов SMF, в частности, при написании управляющих методов (скриптов) и сервисных манифестов нас подстерегает одна маленькая, но довольно неприятная проблема.

Речь идет, в частности, о поведении самописных SMF-сервисов при остановке/рестарте/перезагрузке - они без видимых причин выпадают по таймауту с ошибкой, приведенной в заголовке статьи: "Method "stop" exited with status 208".

С чем это связано и почему так?

Первое столкновение произошло при модификации SMF-сервиса Nessus 3.2.1 (существует версия для Solaris и SMF в ней написан не слишком аккуратно - его пришлось править, но это отдельная история). Сервис некорректно уходил в состояние maintenance при остановке.

Вскрытие показало следующее - в журнале сервиса было вот такое сообщение:

[ May 22 18:27:51 Executing stop method ("/etc/init.d/nessusd stop") ]
[ May 22 18:27:51 Method "stop" exited with status 208 ]

При этом запуск сервиса выполняется совершенно нормально:

[ May 22 21:08:21 Executing start method ("/etc/init.d/nessusd start") ]
[ May 22 21:08:21 Method "start" exited with status 0 ]

Простейший поиск в Гугле привел к следующим результатам:

Результат 1
Результат 2
Результат 3

Наиболее внятно описывает проблему первый результат, если оставить в покое воду и перейти к сути - управляющий метод, в случае, когда сервис достаточно просто убить, не должен этого делать явно. Это должно быть описано в сервисном манифесте. Ну что же, открываем смотрим:

Метод:

stop_proc ()
{
# Stop nessus daemon
pkill nessusd
}

.....

'stop')
# This condition never occurs, because of daemon has not pid-file.
#test -f /opt/nessus/var/nessus/nessusd.pid && kill `cat /opt/nessus/var/nessus/nessusd.pid`
stop_proc
;;

Сервисный манифест (символы тэгов не показаны):

exec_method
type='method'
name='stop'
exec='/etc/init.d/nessusd %m'
timeout_seconds='60'

exec_method
type='method'
name='restart'
exec='/etc/init.d/nessusd %m'
timeout_seconds='60'

Итак, при остановке у нас вызывается функция stop_proc (), использующая pkill. Она-то и не нравится SMF. Причем что интересно - прямое выполнение скрипта (не из SMF) выполняет и запуск, и останов и рестарт - корректно. Даже если усилить нажим и написать pkill -KILL nessusd. Однако, при вызове скрипта из SMF в последнем случае вываливается чертовски непонятное сообщение:

[ May 22 21:16:37 Executing stop method ("/etc/init.d/nessusd stop") ]
[ May 22 21:16:37 Method "stop" failed due to signal KILL ]

Хотя уже все понятно. Pkill возвращает код возврата:

The following exit values are returned:
0 One or more processes were matched.
1 No processes were matched.
2 Invalid command line options were specified.
3 A fatal error occurred.

однако SMF его игнорирует, в последнем случае он ловит сообщение Killed от pkill и тоже не считает его корректным завершением команды.

Всякие игры около подпрограммы остановки процесса, наподобие:

(ужасный кусок извращения)

pkill nessusd>/dev/null 2>&1
retcode=`$ECHO $?`
case "$retcode" in
0) exit 0;;
*) exit 1;;
esac

(другой кусок, не лучше)

[ `pkill nessusd` ] && exit 0

или, тем более, попытки вывести выполнение скрипта на финализирующий exit 0 (хотя данный кусок не показан - уверяю вас, скрипт в оригинале завершается именно командой exit 0) - никакого заметного воздействия, разумеется, не возымели.

Что ж, воспоследуем совету, найденному на вершине поиска Гугл и просто слегка подправим сервисный манифест:

exec_method
type='method'
name='stop'
exec=':kill'
timeout_seconds='60'

Только в этом месте и только вызов метода exec (сам код скрипта мы вообще никак не модифицируем). Что сие означает? Означает это, что для остановки сервиса (который мы так и так убиваем) мы вызываем нативный метод SMF :kill.

Замечание: Я подчеркиваю - этот способ годится лишь для сервисов, которые либо не имеют родных методов остановки или управления процессом/процессами, либо для сервисов, которые не сохраняют никаких состояний или оно нам безразлично.

Проверяем остановку сервиса:

[ Jun 29 17:25:43 Executing stop method (:kill) ]
[ Jun 29 17:25:47 Method "stop" exited with status 0 ]

Все отлично, и остановка и перезапуск сервиса работает гладко:

[ Jun 29 17:49:41 Stopping because service restarting. ]
[ Jun 29 17:49:41 Executing stop method (:kill) ]
[ Jun 29 17:49:41 Executing start method ("/etc/init.d/nessusd start") ]
[ Jun 29 17:49:41 Method "start" exited with status 0 ]

Вот и все решение проблемы.

Как говорится, "Фигово, когда много знаешь" или "Keep it simple, stoopid"! ;)

PS. Хотя это может показаться самоочевидным, тем не менее подчеркну - логи (не обязательно сервисов SMF) - весьма полезная вещь при анализе проблем, не стоит ограничиваться лишь диагностическими сообщениями svcs -xv, как делают многие.