В общем, есть программа nagios для мониторинга, в ней есть нотификации о падении сервисов (ну и вообще настраивается). И можно даже гибко добавлять то, что нужно. Самое удобное - jabber (xmpp) и sms. email у него из коробки есть. Да и email отправляет в очередь почтового сервера, а вот если просто использовать скрипты для отправки, то может случиться так, что нет инета, недоступен сервер jabber или sms api. Но при этом скрипт отработал, хоть и вывалился с ошибкой. С отправлением на email всё понятно. Там отправляется всё на почтарь и висит в его очереди. Что ж. Нам тогда тоже нужна очередь. :)
В качестве очереди нужно было что-то довольно простое и которое умело бы удобный интерфейс командной строки. Вроде бы подошёл redis. Ну на самом деле, положить в очередь:
Взять из очереди:redis-cli rpush queue "test message"
redis-cli lpop queue
В общем, очередь у нас получилась легко. Ну и главный критерий при выборе инструментов был, чтобы получалось всё легко. Далее, эту очередь нужно было обрабатывать. Я заранее не знал, какой формат я там выдумаю, а парсить на баше не очень то и хотелось, поэтому я хотел было взять старый добрый picolisp, но передумал, и взял elisp (это тот, на котором построен emacs). И вот что получилось:
#!/usr/local/sbin/emacs-clean --script
; -*- mode: Emacs-Lisp -*-
(defun message-as-string (message)
(with-output-to-string
(let ((first (car message))
(rest (cdr message)))
(if first
(princ first)
(princ ""))
(dolist (line rest)
(terpri)
(if line
(princ line)
(princ ""))))))
(defun receive-from-queue (queue-name)
(process-lines "redis-cli" "lpop" queue-name))
(defun send-to-queue (queue-name message)
(process-lines "redis-cli" "rpush" queue-name (message-as-string message)))
(defun tmp-queue (queue-name)
(concat queue-name "-tmp"))
(defun process-mail-message (message)
t)
(defun process-xmpp-message (message)
(condition-case nil
(progn
(process-lines "send-xmpp" (car message) (message-as-string (cdr message)))
t)
(error nil)))
(defun process-sms-message (message)
(condition-case nil
(progn
(process-lines "send-sms" (car message) (message-as-string (cdr message)))
t)
(error nil)))
(defun process-message (queue-name message)
(let ((success-state (pcase queue-name
("mail" (process-mail-message message))
("xmpp" (process-xmpp-message message))
("sms" (process-sms-message message)))))
(unless success-state
(send-to-queue (tmp-queue queue-name) message))))
(defun process-queue (queue-name loop-queue)
(while (let ((next-in-queue (receive-from-queue queue-name)))
(unless (equal '("") next-in-queue)
(process-message queue-name next-in-queue)
loop-queue)))
(while (let ((next-in-tmp-queue (receive-from-queue (tmp-queue queue-name))))
(unless (equal '("") next-in-tmp-queue)
(send-to-queue queue-name next-in-tmp-queue)
t))))
(pcase (length argv)
(0 (dolist (queue '("xmpp" "sms"))
(process-queue queue t)))
(1 (process-queue (car argv) nil))
(_ (progn
(princ "Usage: process-queue")
(terpri))))
Смысл заключается в том, чтобы прочитать из очереди по типу сообщения (xmpp, sms), а потом вызвать программу для доставки. Если же произошла какая-то ошибка, то вернуть обратно в очередь, которую затем обработает этот же скрипт, запустившись в кроне.
XMPP решил отправлять питоновским скриптом, ибо он нагуглился быстрее всего. Я его немного, правда, модифицировал, чтобы конфиги читал, да и адресов несколько было:
Конфиг - это обычный INI файл. Для отправки SMS использовался сервис LetsAds. У них есть API, как и у всех. Туда просто отправлял через curl:#!/usr/bin/python
import sys, xmpp, ConfigParser
if len(sys.argv) != 3:
print("usage: %s" % sys.argv[0])
sys.exit(1)
config = ConfigParser.ConfigParser()
config.read("/etc/send-xmpp.ini")
xmpp_jid = config.get("general", "jid")
xmpp_pwd = config.get("general", "password")
toliststring = sys.argv[1]
tolist = toliststring.split(",")
msg = sys.argv[2]
jid = xmpp.protocol.JID(xmpp_jid)
client = xmpp.Client(jid.getDomain(), debug = [])
client.connect()
client.auth(jid.getNode(), str(xmpp_pwd), resource = "SrvMonitor")
for to in tolist:
message = xmpp.protocol.Message(to, msg)
message.setAttr("type", "chat")
client.send(message)
client.disconnect()
#!/bin/bashНу и последний штрих - скрипт, отправляющий в очередь:
if [[ $# -ne 2 ]]; then
echo "Usage: $0 <phone> <message>"
exit 1
fi
source /etc/send-sms.conf
TO=$1
MSG=$2
URL="http://letsads.com/api"
XML="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<request>
<auth>
<login>$PHONE</login>
<password>$PASS</password>
</auth>
<message>
<from>$FROM</from>
<text>$MSG</text>
<recipient>$TO</recipient>
</message>
</request>"
CURLRESULT=`curl --silent --data "$XML" $URL`
CURLEXITCODE=$?
RESULTERROR=`echo "$CURLRESULT"|grep '<name>Error</name>'`
if [[ ! $CURLEXITCODE -eq 0 ]] || [[ ! -z "$RESULTERROR" ]]; then
exit 1
fi
#!/bin/shСобственно, он и используется в самом нагиосе для того, чтобы положить в очередь:
QUEUE_NAME=$1
TO=$2
MESSAGE=$3
redis-cli rpush "$QUEUE_NAME" "`printf "%s\n%s" "$TO" "$MESSAGE"`"
process-queue "$QUEUE_NAME"
/usr/local/sbin/queue-notification sms "$_CONTACTPHONE$" "$NOTIFICATIONTYPE$ : $HOSTALIAS$/$SERVICEDESC$ is $SERVICESTATE$ ($OUTPUT$)"В общем, всё довольно просто и прозрачно. Для пущей надёжности можно было бы ещё добавить сохранение состояния редиса после запихивания сообщения в очередь, но этим уже страдать не стал.