Asterisk + LUA: быстрый старт

За последний год на Хабре появилось несколько статей про использование диалплана lua в asterisk (раз, два, три, четыре). Это достаточно интересный способ написания гибких и мощных диалпланов. Но чтобы попробовать такой способ написания диалпланов надо потратить некоторое количество времени: установить нужные библиотеки, пересобрать с необходимыми опциям астериск.

Вдобавок у многих пользователей asterisk’а разный уровень подготовки: кто-то ближе к системному администрированию или даже к традиционной телефонии, чем к программированию. Плюс специфика телефонии — лучше лишний раз незнакомыми экспериментами не нагружать работающие системы, а проводить тесты и эксперименты на своем ноутбуке — приходится захламлять систему. В общем, есть немало причин «отложить на потом».

В данной статье я хочу показать всем желающим и работающим с астериском, как, используя docker, можно быстро ощутить вкус гибких сценариев lua. А уж затем решить стоит этим пользоваться дальше на практике или нет. (Кому неинтересно читать, а интересно смотреть и слушать — в конце текста 6-минутное видео с основными моментами и результатом.)

2ca6682ead5b4f3cbd0a5eca2990e112.png

Вводное слово


В рамках работы над несколькими своими проектами, следуя современной тенденции упаковки всего в контейнеры, я подготовил образ astolua (asterisk + lua). В Dockerfile приведены команды для установки asterisk 11, lua 5.1, luarocks (пакетный менеджер для lua), luamongo (драйвер для доступа к mongodb), некоторые пакеты lua rocks. Вы можете в дальнейшем в репозитории docker-astolua взять только полезное для себя и собрать свою рабочую лошадку.

Безусловно плюсом докера является возможность скачать образ, провести пробы-эксперименты-тесты, а затем удалить образы, оставив свою операционную систему в чистоте и привычном порядке.

На основе образа astolua мы создадим свой рабочий образ, в котором будем использовать тестовые файлы конфигурации астериска и диалплан на lua.

Подготовка


Нам потребуется docker. Если у вас он не установлен, то, пожалуйста, установите сначала docker (официальная документация, статья на Хабре).

Также нам потребуется git (установка git)

Также сразу отмечу, что моей рабочей системой является Ubuntu 14.04. Если вы используете иной Linux, то отличия в командах по идее быть не должно, но нюансы не исключены.

Загрузка образа astolua


Затягиваем образ (внимание, будет скачан образ с репозитория hub.docker.com размером ~600Мб).

docker pull antirek/astolua

Sample


Клонируем docker-astolua-sample — это заранее заготовленный набор файлов для этой статьи.

git clone https://github.com/antirek/docker-astolua-sample.git
cd docker-astolua-sample

Теперь давайте остановимся на sample и посмотрим содержимое директории.

Файл Dockerfile

Файл для сборки нашего рабочего образа. В нем мы указываем, что берем за основу astolua. Затем добавляем скрипт автозагрузки after_start.sh, который будет выполнен при старте контейнера. В консоль, где мы запустим контейнер, будт выводиться лог консоли астериска.

Файл build

Внутри файла команда докера на построение образа sample из нашего Dockerfile.

docker build -t "astolua:sample" .

Файл run
Внутри файла команда докера на запуск контейнера на основе образа sample с конфигурированием необходимых ему ресурсов.

docker run \
 -v /etc/localtime:/etc/localtime:ro \
 -v $(pwd)/store/etc/asterisk:/etc/asterisk \
 -v $(pwd)/store/var/log/asterisk:/var/log/asterisk \
 -v $(pwd)/store/var/menu:/var/menu/ \
 --net=host \
 -i -t "astolua:sample"

Папка store
Папка store содержит конфигурационные файлы астериска (те, которые обычно лежат в /etc/asterisk) и папки для логов и голосовых меню.

Команда run наиболее интересна, т.к. здесь указываются необходимые ресурсы для контейнера. Например, опцией -v $(pwd)/store/etc/asterisk:/etc/asterisk мы указываем, что конфигурационные файлы из нашей папки store должны оказаться внутри контейнера на своем месте в /etc/asterisk.

Почему команды в файлах? Удобно редактировать команды в файлах, т.к. это ускоряет время на протестировать изменения в командах с разными опциями, а также все изменения лягут под контроль версий. И еще удобно потом перенести опции в docker-compose, если образ будет использоваться совместно с другими.

Вернемся к консоли.

Сделаем образ astolua: sample (в директории, куда мы склонировали docker-astolua-sample)

./build

Запускаем asterisk (если у вас уже запущен на машине астериск или иной сервис, занимающий порт 5060, то его лучше предварительно остановить)

./run

В консоль должен повалиться лог загрузки астериска. Можно протестировать связь.

В конфигурационном файле астериска sip.conf указаны два абонента 101 и 102 (пароль 1234), а в файле queues.conf очередь 1234, в которую добавлены эти два абонента. Настройте свой софтфон или хардфон на 101 абонента и попробуйте совершить звонок на абонента 102. (Транков для подключения к внешним voip-сервисам или настроек какого-либо железа нет, поэтому диалплан мы потестируем на локальных звонках). Информация о звонке между абонентами должна появиться в консоли астериска.

Абоненты работают, звонки проходят? Ок, значит астериск в докер-контейнере работает как надо.

Диалплан lua


Диалплан lua находится в файле extensions.lua. В конфигурационных файлах астериска в папке store/etc/asterisk есть пример работающего диалплана lua.

В этом файле должны быть правильно описаны переменные extensions и hints (в терминологии lua — это «таблицы»).

В таблице extensions содержатся контексты и соответствующие extensions. Все как в традиционном диалплане. Но каждый extension обрабатывается своей функцией, в которой вы уже можете делать все, что угодно на lua, при этом взаимодействуя с астериском через таблицы app и channel.

Самый простой пример

extensions = {
    ["internal"] = {
        ["_1XX"] = function (context, extension)
            -- do something --
            app.dial('SIP/'..extension);
            -- do something again
        end;
    }
}

Видно, что через app доступно приложение диалплана Dial, оно принимает все те же параметры, что и в традиционном диалплане. Через app доступны все приложения диалплана.

Переменная channel дает доступ к канальным переменным. Вот так, например, получаем dialstatus.

extensions = {
    ["internal"] = {
        ["_1XX"] = function (context, extension)
            -- do something --            
            app.dial('SIP/'..extension);
            local dialstatus = channel["DIALSTATUS"]:get();
            app.noop('dialstatus: '..dialstatus);
        end;
    }
}

Вы можете изменить extensions.lua, а затем командой в CLI астериска module reload pbx_lua.so перечитать extensions.lua. Астериск проверит синтаксис lua, и если все ок, то загрузит его для выполнения — можно тестировать изменения.

А что еще можно делать в диалплане lua?

Например, гибко обработать dialstatus, который будет возвращен функцией Dial диалплана. Не надо больше изобретать эти Goto (s-${DIALSTATUS},1), теперь можно по-человечески написать проверку статуса

extensions = {
    ["internal"] = {
        ["_1XX"] = function (context, extension)
            app.dial('SIP/'..extension);

            local dialstatus = channel["DIALSTATUS"]:get();
            if dialstatus == 'BUSY' then
                -- do something       
            elseif dialstatus == 'CHANUNAVAIL' then 
                -- do another thing
            end;
        end;
    }
}

В примерe extensions.lua есть пример простого ivr: позвонив на номер 200, вы услышите запись из файла /var/menu/demo и сможете перейти дальше, нажав 1 или 2.

local ivr = function (context, extension)        
    app.read("IVR_CHOOSE", "/var/menu/demo", 1, nil, 2, 3);
    local choose = channel["IVR_CHOOSE"]:get();

    if choose == '1' then
        app.queue('1234');
    elseif choose == '2' then
        dial('internal', '101');
    else
        app.hangup();
    end;
end;

Для человека, написавшего пару десятков строк традиционного диалплана, здесь должно быть все знакомо. Плюс появляется вся мощь lua и пакетов luarocks. Надеюсь очевидно, что здесь же в диалплане вы можете отправить смс, емейл, положить данные в бд, взять данные из бд, а бд может быть любая: mysql, mongodb, redis и т.п., сделать вызов команды, инициировать еще один звонок, сделать крутой роутинг звонка по транкам и т.д., не забывая, конечно, что это все работает в рамках астериска, и все «тяжелые» задачи лучше все-таки решать отдельно.

Что дальше?


Предлагаю:

Надеюсь, данная статья будет полезна для быстрого старта, и вы найдете один свободный зимний вечер и попробуете такой способ написания диалпланов.

Ошибки? Опечятки? Вопросы? Пожалуйста, пишите.

© Habrahabr.ru