пятница, 22 августа 2014 г.

Nagios, нотификация по jabber (xmpp), sms и языковый интернационал

В общем, есть программа 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 решил отправлять питоновским скриптом, ибо он нагуглился быстрее всего. Я его немного, правда, модифицировал, чтобы конфиги читал, да и адресов несколько было:
#!/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()
Конфиг - это обычный INI файл. Для отправки SMS использовался сервис LetsAds. У них есть API, как и у всех. Туда просто отправлял через curl:
#!/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$)"
В общем, всё довольно просто и прозрачно. Для пущей надёжности можно было бы ещё добавить сохранение состояния редиса после запихивания сообщения в очередь, но этим уже страдать не стал.

Комментариев нет:

Отправить комментарий