пятница, 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$)"
В общем, всё довольно просто и прозрачно. Для пущей надёжности можно было бы ещё добавить сохранение состояния редиса после запихивания сообщения в очередь, но этим уже страдать не стал.

четверг, 21 августа 2014 г.

Пресс снова бросить курить

Как только не переведут... Или ещё раз о сложностях перевода. Такое вот увидел на одном планшете IconBIT. Довольно долго не мог понять как звучала фраза в оригинале:


среда, 20 августа 2014 г.

Модный апач и добрый пиколисп

В общем, так вышло, что пришлось обновляться. Обновился также и апач до версии 2.4. В старой версии 2.2 ещё продолжал работать модуль mod_auth_pam и было очень удобно, что я имел аутентификацию такую же, как и системную. В новой версии этот модуль за каким-то лядом выпили и всё перестало работать. Ну ляд там вполне понятный, конечно, кривой код, кривой в итоге модуль. Рекомендовали вместо этого использовать mod_authnz_external. Это, конечно, не то, но тоже решение. Программка pwauth для проверки пользователя по паролю была в комплекте, а вот ещё нужная вещь ограничение по группам пользователя не нашлось. Но нашёлся какой-то перловый скрипт, который шёл в исходниках этого модуля, который почему-то не заработал. Может, поэтому его и не включили в дистрибутивный пакет. Пришлось писать. Основная идея - берем с помощью getent группы и пользователей и создаём списки групп, куда входят как пользователи, которые участвют в списке, так и те, чья группа является основной.
Для реализации пришлось думать что брать. Нужно было, чтобы можно было обрабатывать списки (lisp, ну как же) и при этом чтобы можно было легко и прозрачно взаимодействовать с системой. В прошлом мне нравился Scheme Shell, но его судьба мне теперь неизвестна, вроде на 64 битных платформах с ним туго, хотя, вроде, есть multiarch, но привлёк некий picolisp - с очень свежим (или не очень) взглядом на мир. В общем, в целом обычный лисп, команды относительно удобно вызываются. Пример вызова "getent passwd":

(in (list 'getent "passwd")
  <какие-то действия>)
 Ну а дальше дело пошло. :) В общем, вот целиком готовый скрипт. Ну и как у меня выглядит конфиг апача, раз уж речь о нём тоже зашла. Это в конфиге virtualhost.
        # External auth
        DefineExternalAuth sysauth pipe /usr/sbin/pwauth
        DefineExternalGroup sysgroup pipe /usr/local/sbin/sysgroup
 А это в конфиге уже непосредственно того ресурса, который нужно разграничивать:
        AuthType Basic
        AuthBasicProvider external
        AuthExternal sysauth
        GroupExternal sysgroup
        AuthName "BAZON.RU projects"

        require external-group developers

вторник, 19 августа 2014 г.

Итальянская мебель

В общем, привезли мебель в магазин, прямо из Италии. Собирали не местные, а командированные сербы. Ну а мы туда своё оборудование понавешали. Кстати, для последнего сделано очень удобно - задняя стенка стола на петлях.

понедельник, 4 августа 2014 г.

ICFPC-2014 - невероятно, но факт!

Ещё одна любительская симуляция. Как ни странно, на ней мы показываем неожиданные для себя результаты (напоминаю, что называемся мы Skobochka). В принципе, это не финальная симуляция оргов, но даже такого результата не ожидали. Если всё так и будет идти, то даже есть ненулевой шанс, что мы засветимся в TOP20. Если это произойдёт, то я буду выглядеть приблизительно так:


суббота, 2 августа 2014 г.

ICFPC-2014 - мелочь, а приятно

В общем, один из чуваков решил не дожидаться официальных результатов и захотел составить неофициальную таблицу. Для этого он просил запустить у себя и скинуть ему цифры. Не поленился swizard, замерял, отправил. Расстраивает, что в общем зачёте нас даже нет, зато на картах мы там где-то тусуемся в пределах видимости топа. А по карте world-2 так вообще на первом месте! :) Об этом уже похвастался swizard у себя в журнале, ну и я тоже не смог удержаться. :)