До тех пока правила маршрутизации звонка статичны всё решается достаточно просто. Но вот когда появляется желание переключить входящий звонок на номер, который надо получить из внешнего источника информации, такого как LDAP или СУБД, начинается всё самое интересное.
Если информация хранится в LDAP, то есть как минимум 3 способа получить её оттуда.
Способ первый и, казалось бы, самый простой, использовать отдельное приложение LDAPget, но, к сожалению оно существует только для asterisk 1.4, а откатываться на 1.4 ну очень не хотелось бы.
Способ второй заключается в хранении целиком диалплана в LDAP, для чего надо настроить res_ldap.conf и extconfig.conf в соответствии с размноженной по всему интернету инструкцией под заголовком Asterisk Realtime Ldap. Но для реализации этого способа необходимо вносить изменения схему, так как штатного набора полей и классов для этого будет недостаточно. К тому же каждую строчку исходного диалплана понадобится преобразовать в отдельную запись в LDAP.
Третий способ, который и был выбран мной, заключался в использовании функций семейства REALTIME. Казалось бы, функции хорошие, но как показала практика не очень хорошо документированы, а периодически попадающиеся рекомендации, относящиеся к asterisk 1.2 и более древним его версиям, только больше запутывают.
Что ж. Приступим. Не самым плохим началом будет проверка наличия этих функций в используемом asterisk:
core show functions like REALTIME
Среди выведенных функций должны быть REALTIME и REALTIME_FIELD, при этом первая позволяет получить все поля исходной записи в LDAP, а вторая - возвращает значение конкретного указанного поля. Вот она то нам и нужна.
Перед тем как воспользоваться этой замечательной функцией необходимо произвести некоторую подготовительную работу.
В первую очередь необходимо настроить секцию [_general] res_ldap.conf:
[_general]
host=127.0.0.1
port=389
protocol=
basedn=dc=tune-it,dc=ru
user=cn=asterisk,ou=profile,dc=tune-it,dc=ru
pass=superpassword
При этом в различных How-To авторы в рамках одного документа умудряются данную секцию называть и [global] и [general], но в конфигах, которые поставились у меня, она названа [_general] и при этом точно работала. Также обращу внимание на тот факт, что авторы asterisk почему-то проигнорировали возможность анонимного bind к LDAP и требуют создания отдельного пользователя и указания user и pass. Более того, если их не указать, то asterisk полностью игнорирует содержимое этого файла, как будто его вообще нет.
Далее в этот же файл необходимо добавить секцию, на которую мы будем ссылаться из extconfig.conf, и в простейшем случае она будет выглядеть примерно так:
[info]
Реальная польза такого требования сомнительна, тем не менее в этой секции можно, например, указать additionalFilter=, который позволит указать дополнительные фильтры при поиске в LDAP.
Далее приступаем к редактированию extconfig.conf, в котором в секцию [settings] добавляем следующее:
[settings]
info => ldap,"dc=tune-it,dc=ru",info
При этом первое упоминание info является именем семейства (family), которое будет потом использоваться в вызовах функций семейства REALTIME. ldap, соответственно, указывает на то, что данные необходимо получать используя Realtime LDAP драйвер. Далее фактически указывается значение basedn из res_ldap.conf, есть ли в этом какой-то смысл мне не очень понятно. В последнем поле указывается то самое имя секции, которое было указано ранее в res_ldap.conf, при этом оно там действительно должно быть, но при этом может быть пустым. В противном случае получим сообщение об ошибке.
После такой подготовки в диалплане наконец-то можно использовать вызов REALTIME_FIELD:
Dial(SIP/${REALTIME_FIELD(info,cn,eng,telephoneNumber)})
Соответственно в первом параметре указывается имя семейства из extconf.conf, далее указываются имя поля записи LDAP и значение, которому оно должно быть равно. В последнем поле указывается имя поля записи, значение которой необходимо вернуть. Что по смыслу идентично результату выполнения команды:
ldapsearch -h localhost -p 389 -b dc=tune-it,dc=ru cn=eng telephoneNumber
Если пытаться получить аналогичным образом данные из горячё любимого мной PostgreSQL, действия будут похожие, но тоже со своей спецификой.
Начать, традиционно, необходимо с настройки res_pgsql.conf:
[general]
dbhost=127.0.0.1
dbport=5432
dbname=asterisk
dbuser=asterisk
dbpass=superpassword
Тут всё оказалось просто и без подводных камней. После чего традиционно приступаем к исправлению extconfig.conf:
[settings]
info => pgsql,asterisk,tablename
info, как мы уже знаем, это имя семейства для вызова REALTIME, pgsql, соответственно, указывает на то, что данные будут получаться из PostgreSQL. В следующем поле указывается имя базы, т.е. значение dbname из res_pgsql.conf. В последнем поле указано имя таблицы, из которой будут выбираться данные.
И собственно получение данных:
Dial(SIP/${REALTIME_FIELD(info,user =,eng,exten)})
Что указывает на необходимость взять данные из таблицы, указанной в семействе info, выполнять операцию сравнения из 2-ого параметра со значением из 3-его. При этом значение третьего параметра будет взято в апострофы. Из результатов запроса будет возвращаться значение поля exten запрошенной таблицы.
При таком вызове REALTIME_FIELD будет сгенерирован такой запрос в СУБД:
SELECT * FROM tablename WHERE user = 'eng';
Особое внимание следует уделить тому, что указано во 2-ом и 3-ем параметрах, в том числе отсутствию лишних пробелов перед и после запятыми. Как не сложно заметить, возможности asterisk по формированию запросов к СУБД весьма ограничены, и если необходимо выбирать данные из нескольких таблиц, то будет необходимо будет создавать VIEW, который можно будет использовать учитывая такой ограниченный синтаксис.
А что делать, если с запросом что-то не так? Об ошибке мы узнаем из чрезвычайно информационного сообщения типа:
WARNING[4110] res_config_pgsql.c: PostgreSQL RealTime: Failed to query 'tablename@asterisk'. Check debug for more info.
Что за debug надо смотреть и как его включить я так и не понял. По крайней мере после раскомментирования в logger.conf строки:
full => notice,warning,error,debug,verbose
информации по проблеме не добавилось. Спасает положение только чтение логов самого PostgreSQL, благо он имеет свойство сообщать о проблемах в запросе.