Глава 13

Интеграция реляционной базы данных

Нет ничего более раздражающего, чем хороший пример.

– Марк Твен

В этой главе мы рассмотрим интеграцию некоторых возможностей и функций Asterisk в базу данных. Для Linux доступно несколько баз данных, но мы решили ограничить наше обсуждение двумя самыми популярными: PostgreSQL и MySQL.

Мы также объясним, как настроить Linux для подключения к базе данных Microsoft SQL через ODBC; однако конфигурация части Windows/Microsoft выходит за рамки этой книги.

Независимо от того, какую базу данных вы используете, эта глава фокусируется в первую очередь на ODBC-коннекторе, поэтому, если у вас есть некоторое знакомство с подготовкой вашей любимой базы данных ODBC, у вас не должно быть никаких проблем с этой главой.

Интеграция Asterisk с базами данных является одним из фундаментальных аспектов построения крупной кластерной или распределенной системы. Мощь базы данных позволит вам использовать динамически изменяющиеся данные в ваших диалпланах для таких задач, как обмен информацией по массиву систем Asterisk или интеграция с веб-сервисами. Наша любимая функция диалплана, которую мы рассмотрим далее в этой главе – func_odbc.

Хотя не для всех развертываний Asterisk потребуются реляционные базы данных, понимание того, как их использовать, открывает сундук с сокровищами, полный новых способов разработки вашего телекоммуникационного решения.

Установка и настройка PostgreSQL и MySQL

В следующих разделах мы покажем, как устанавливать и настраивать PostgreSQL и MySQL как для RHEL, так и для Ubuntu.1 Рекомендуется устанавливать только одну базу данных одновременно, работая через этот раздел. Выберите базу данных, которая вам наиболее удобна, так как нет неправильного выбора.

Установка PostgreSQL для RHEL

Следующая команда может быть использована для установки сервера PostgreSQL и его зависимостей с консоли:

$ sudo yum install postgresql-server

Install 3 Package(s)

Upgrade 0 Package(s)

Total download size: 6.3 M

Installed size: 29 M

Is this ok [y/N]: y

После установки базы данных вы должны ее инициализировать:

$ sudo service postgresql initdb

Затем запустите базу данных:

$ sudo service postgresql start

Теперь перейдите в раздел “Настройка PostgreSQL” для получения инструкций о том, как выполнить первоначальную настройку.

Установка PostgreSQL для Ubuntu

Чтобы установить PostgreSQL на Ubuntu выполните следующую команду. Вам также будет предложено установить любые дополнительные пакеты зависимостей. Нажмите Enter чтобы принять список зависимостей, после чего пакеты будут установлены, и PostgreSQL будет автоматически запущен и инициализирован:

$ sudo apt-get install postgresql

After this operation, 19.1MB of additional disk space will be used.

Do you want to continue [Y/n]? y

Теперь перейдите в раздел “Настройка PostgreSQL” для получения инструкций о том, как выполнить первоначальную настройку.

Установка MySQL для RHEL

Чтобы установить MySQL на RHEL, выполните следующую команду. Вам будет предложено установить несколько зависимостей. Нажмите Enter чтобы принять, и будет установлен MySQL сервер и зависимости пакетов:

$ sudo yum install mysql-server

Install 3 Package(s)

Upgrade 0 Package(s)

Total download size: 9.6 M

Installed size: 27 M

Is this ok [y/N]: y

Затем запустите базу данных MySQL, запустив:

$ sudo service mysqld start

Теперь перейдите в “Настройка MySQL” для выполнения начальной конфигурации.

Установка MySQL для Ubuntu

Для установки MySQL на Ubuntu выполните следующую команду. Вам будет предложено установить несколько зависимостей. Нажмите Enter чтобы согласиться, и MySQL сервер и его пакеты зависимостей будут установлены:

$ sudo apt-get install mysql-server

Need to get 24.0MB of archives.

After this operation, 60.6MB of additional disk space will be used.

Do you want to continue [Y/n]? y

Во время установки вы будете помещены в мастер настройки, чтобы помочь вам с начальной конфигурацией базы данных. Вам будет предложено ввести новый пароль для пользователя root. Введите сложный пароль и нажмите Enter. Затем вас попросят подтвердить пароль. Введите свой сложный пароль еще раз, а затем Enter. Затем вы вернетесь в консоль где будет завершена установка. Теперь будет запущен сервис MySQL.

Теперь перейдите в “Настройка MySQL” для выполнения начальной конфигурации.

Настройка PostgreSQL

Далее создайте пользователя с именем asterisk, которого вы будете использовать для подключения к базе данных и управления ею. Вы можете переключиться на пользователя postgres, используя следующую команду:

$ sudo su – postgres

На момент написания этой статьи в RHEL и Ubuntu используется PostgreSQL версии 8.4.x.

Затем запустите следующие команды для создания пользователя asterisk в базе данных и установите разрешения:

$ createuser -P

Enter name of user to add: asterisk

Enter password for new user:

Enter it again:

Shall the new role be a superuser? (y/n) n

Shall the new user be allowed to create databases? (y/n) y

Shall the new user be allowed to create more new users? (y/n) n

CREATE ROLE

Теперь отредактируйте файл pg_hba.conf, чтобы разрешить пользователю asterisk, которого вы только что создали, подключение к серверу PostgreSQL через сокет TCP/IP.

В RHEL этот файл будет расположен по адресу /var/lib/pgsql/data/pg_hba.conf. На Ubuntu вы найдете его на /etc/postgresql/8.4/main/pg_hba.conf.

В конце файла замените все строки ниже этой:

# TYPE DATABASE USER CIDR-ADDRESS METHOD

на следующее:

# TYPE DATABASE USER CIDR-ADDRESS METHOD

host all asterisk 127.0.0.1/32 md5

local all asterisk trust

Настройка доступа к базе данных PostgreSQL через IPv6 localhost

Кроме того, на Ubuntu вам, скорее всего, придется добавить следующую строку:

host all asterisk ::1/128 md5

Без него при “Проверке ODBC-коннектора” может возникнуть следующая ошибка при подключении:

[28000][unixODBC]FATAL:no pg_hba.conf entry for host “::1”, user “asterisk”,

database “asterisk”, SSL off

[ISQL]ERROR: Could not SQLConnect

Теперь вы можете создать базу данных, которую мы будем использовать в этой главе. Вызовите базу данных asterisk и установите владельцем вашего пользователя asterisk:

$ createdb –owner = asterisk asterisk

CREATE DATABASE

Вы можете установить пароль для пользователя asterisk следующим образом:

$ psql -d template1

template1=# “ALTER USER asterisk WITH PASSWORDpassword'”

template1=# \q

Выход из пользователя postgres:

$ exit

Затем перезапустите сервер PostgreSQL. В RHEL:

$ sudo service postgresql restart

Вам необходимо перезапустить службу PostgreSQL, потому что вы внесли изменения в pg_hba.conf, а не потому что вы добавили нового пользователя или сменили пароль.

В Ubuntu:

$ sudo /etc/init.d/postgresql restart

Вы можете проверить свое соединение с сервером PostgreSQL через TCP/IP, например:

$ psql -h 127.0.0.1 -U asterisk

Пароль для пользователя asterisk:

Добро пожаловать в psql 9.1 , интерактивный терминал PostgreSQL.

Тип: \copyright для условий распространения

\h для справки по командам SQL

\? для получения помощи по командам psql

\g или terminate с точкой с запятой для выполнения запроса

\q для выхода

asterisk =>

Теперь вы готовы перейти к разделу “Установка и настройка ODBC“.

Настройка MySQL

Теперь, когда база данных MySQL работает, вы должны защитить свою инсталляцию. Удобно, для этого есть скрипт, который вы можете выполнить, что позволит ввести новый пароль2 пользователя root наряду с некоторыми дополнительными опциями. Скрипт довольно прост, и после ввода и подтверждения пароля root вы можете продолжать выбирать значения по умолчанию, если у вас нет конкретной причины.

Выполните следующий скрипт:

$ sudo /usr/bin/mysql_secure_installation

Затем подключитесь к консоли базы данных, чтобы вы могли создать своего пользователя asterisk и установить разрешения:

$ mysql -u root -p

Enter password:

После ввода пароля вам будет представлена подсказка mysql в консоли. Теперь вы можете создать своего пользователя asterisk, выполнив команду CREATE USER. % – символ шаблона с указанием пользователя asterisk может подключаться с любого хоста и IDENTIFIED BY паролем some_secret_password (который вы явно должны изменить). Обратите внимание на конечную точку с запятой:

mysql> CREATE USER ‘asterisk’@’%’ IDENTIFIED BY ‘some_secret_password’;

Query OK, 0 rows affected (0.00 sec).

Давайте также создадим исходную базу данных, которую вы будете использовать в этой главе:

mysql> CREATE DATABASE asterisk;

Query OK, 1 rows affected (0.00 sec.)

Теперь, когда вы создали своего пользователя и базу данных, необходимо назначить разрешения для пользователя asterisk для доступа к базе данных asterisk:

mysql> GRANT ALL PRIVILEGES ON asterisk.* TO ‘asterisk’@’%’;

Query OK, 0 rows affected (0.00 sec)

Наконец, выйдите из консоли и убедитесь, что ваши права верны, выполнив вход в базу данных asterisk в качестве пользователя asterisk:

mysql> exit

Bye

# mysql -u asterisk -p asterisk

Enter password:

mysql>

Этот пароль вам понадобится при настройке и тестировании ODBC-коннектора, поэтому сохраните его. Теперь вы готовы перейти к следующему разделу.

Установка и настройка ODBC

ODBC-коннектор представляет собой уровень абстракции базы данных, который позволяет Asterisk обмениваться данными с широким спектром баз данных, не требуя от разработчиков создания отдельного коннектора для каждой поддерживаемой Asterisk базы данных. Это экономит много усилий по разработке и обслуживанию кода. Существует небольшая стоимость производительности, потому что мы добавляем еще один прикладной уровень между Asterisk и базой данных, но это можно смягчить с помощью правильной конструкции и стоимости этого, когда вам нужны мощные и гибкие возможности базы данных в вашей системе Asterisk.

Перед установкой коннектора в Asterisk вам необходимо установить ODBC в сам Linux.

Чтобы установить драйверы ODBC, используйте одну из следующих команд.

На RHEL:

$ sudo yum install unixODBC unixODBC-devel libtool-ltdl libtool-ltdl-devel

На Ubuntu:

$ sudo apt-get install unixODBC unixODBC-dev

См. Главу для просмотра матрицы пакетов, которые вы должны установить.

Вам также потребуется установить unixODBC пакет разработки, поскольку Asterisk использует его для создания модулей ODBC, которые мы будем использовать в этой главе.

Драйверы unixODBC, поставляемые с дистрибутивами, часто отличаются от официально выпущенных версий, доступных на веб-сайте unixODBC.

Если у вас есть проблемы со стабильностью при использовании unixODBC, вам может потребоваться установить из исходников. Просто сначала удалите драйверы unixODBC через диспетчер пакетов, а затем обновите пути в файле /etc/odbcinst.ini.

Чтобы установить ODBC-коннектор MySQL на RHEL:

$ sudo yum install mysql-connector-odbc

Для установки ODBC-коннектора PostgreSQL на RHEL:

$ sudo yum install postgresql-odbc

Для установки ODBC-коннектора PostgreSQLна Ubuntu:

$ sudo apt-get install odbc-postgresql

Или установить ODBC-коннектор MySQL на Ubuntu:

$ sudo apt-get install libmyodbc

Настройка ODBC для PostgreSQL

Конфигурация для драйвера ODBC PostgreSQL выполняется в файле /etc/odbcinst.ini.

В RHEL файл по умолчанию уже содержит некоторые данные, в том числе для PostgreSQL, поэтому просто убедитесь, что данные существуют. Файл будет выглядеть следующим образом:

[PostgreSQL]

Description = ODBC for PostgreSQL

Driver = /usr/lib/psqlodbc.so

Setup = /usr/lib/libodbcpsqlS.so

Driver64 = /usr/lib64/psqlodbc.so

Setup64 = /usr/lib64/libodbcpsqlS.so

FileUsage = 1

В Ubuntu файл /etc/odbcinst.ini будет пустым, поэтому вам нужно будет добавить данные в этот файл конфигурации. Добавьте в odbcinst.ini следующее:

[PostgreSQL]

Description = ODBC for PostgreSQL

Driver = /usr/lib/odbc/psqlodbca.so

Setup = /usr/lib/odbc/libodbcpsqlS.so

FileUsage = 1

В 64-битных системах , вам нужно будет изменить путь библиотек из /usr/lib на /usr/lib64 , чтобы получить доступ к файлам с правильной библиотекой.

В любом случае вы можете использовать cat> /etc/odbcinst.ini для записи чистого файла конфигурации, как это было сделано в других главах. Просто используйте Ctrl + D, чтобы сохранить файл, как только закончите.

Убедитесь, что система видит драйвер, выполнив следующую команду. Она должна вернуть имя метки PostgreSQL, если все хорошо:

$ odbcinst -q -d

[PostgreSQL]

Затем настройте файл /etc/odbc.ini, который используется для создания идентификатора, который Asterisk будет использовать для ссылки на эту конфигурацию. Если в любой момент в будущем вам нужно изменить базу данных на что-то еще, то нужно будет просто изменить этот файл, чтобы Asterisk продолжал указывать на то же место:3

[asterisk-connector]

Description = PostgreSQL connection to ‘asterisk’ database

Driver = PostgreSQL

Database = asterisk

Servername = localhost

Port = 5432

Protocol = 8.1

ReadOnly = No

RowVersioning = No

ShowSystemTables = No

ShowOidColumn = No

FakeOidIndex = No

ConnSettings =

Настройка ODBC для MySQL

Конфигурация для MySQL ODBC-драйвера находится в файле /etc/odbcinst.ini.

В RHEL файл по умолчанию уже содержит некоторые данные, в том числе для MySQL, но он должен быть раскоментирован и требует нескольких изменений. Замените существующий текст следующим:

[MySQL]

Description = ODBC for MySQL

Driver = /usr/lib/libmyodbc5.so

Setup = /usr/lib/libodbcmyS.so

Driver64 = /usr/lib64/libmyodbc5.so

Setup64 = /usr/lib64/libodbcmyS.so

FileUsage = 1

В Ubuntu /etc/odbcinst.ini будет пустым, поэтому вам нужно будет добавить данные в этот файл конфигурации. Добавьте в odbcinst.ini следующее:

[MySQL]

Description = ODBC for MySQL

Driver = /usr/lib/odbc/libmyodbc.so

Setup = /usr/lib/odbc/libodbcmyS.so

FileUsage = 1

В 64-битных системах , вам нужно будет изменить путь библиотек из /usr/lib на /usr/lib64, чтобы получить доступ к файлам с правильной библиотекой.

В любом случае вы можете использовать cat> /etc/odbcinst.ini для записи чистого файла конфигурации, как это было сделано в других главах. Просто используйте Ctrl + D, чтобы сохранить файл, как только вы закончите.

Убедитесь, что система видит драйвер, выполнив следующую команду. Она должна вернуть имя метки MySQL, если все хорошо:

$ odbcinst -q -d

[MySQL]

Затем настройте файл /etc/odbc.ini, который используется для создания идентификатора, который Asterisk будет использовать для ссылки на эту конфигурацию. Если в любой момент в будущем вам нужно изменить базу данных на что-то еще, то нужно будет просто перенастроить этот файл, чтобы Asterisk продолжал указывать на то же место:

[asterisk-connector]

Description = MySQL connection to ‘asterisk’ database

Driver = MySQL

Database = asterisk

Server = localhost

Port = 3306

Socket = /var/lib/mysql/mysql.sock

Местоположение этого файла может быть другим, поэтому вам может потребоваться найти его в вашей системе.

На Ubuntu местоположение сокета – /var/run/mysqld/mysqld.sock.

Настройка ODBC для Microsoft SQL

Подключение к Microsoft SQL (MS SQL) аналогично подключению к MySQL или PostgreSQL, как мы обсуждали ранее. Конфигурация MS SQL выходит за рамки этой книги, но следующая информация позволит настроить ваш Asterisk для подключения к базе данных MS SQL после включения соответствующих разрешений для этой базы данных.

Чтобы подключиться к MS SQL, вам необходимо установить драйверы FreeTDS с помощью диспетчера пакетов (или путем компиляции исходных файлов, доступных по адресу http://www.freetds.org).

На RHEL:

$ sudo yum install freetds

On Ubuntu:

$ sudo apt-get install tdsodbc

После установки драйверов вам необходимо поправить файл /etc/odbcinst.ini, в котором сообщается системе, где находятся файлы драйверов.

Вставьте следующий текст в /etc/odbcinst.ini помощью вашего любимого текстового редактора или с помощью следующей команды:

$ sudo cat> /etc/odbcinst.ini

[FreeTDS]

Description = ODBC для Microsoft SQL

Driver = / usr / lib / i386-linux-gnu / odbc / libtdsodbc.so

UsageCount = 1

Threading = 2

Ctrl+D

Значение драйвера должно быть /usr/lib/x86_64-linux-gnu/ если вы используете 64-разрядную установку. Если вы скомпилировали из исходных кодов, файлы могут быть расположены в каталоге /usr/local/lib/ или (если вы скомпилировали в 64-разрядной системе) /usr/local/lib64/.

Убедитесь, что система может видеть драйвер, выполнив следующую команду. Она должна вернуть имя метки FreeTDS, если все в порядке:

$ odbcinst -q -d

[FreeTDS]

После настройки драйверов вам необходимо изменить /etc/odbc.ini для управления подключением к базе данных:

[asterisk-connector]

Description = MS SQL connection to ‘asterisk’ database

Driver = FreeTDS

Database = asterisk

Server = 192.168.100.1

Trace = No

TDS_Version = 7.0

Port = 1433

В следующем разделе вы сможете проверить свое соединение с MS SQL-сервером.

Проверка ODBC-коннектора

Теперь убедитесь, что вы можете подключиться к своей базе данных с помощью приложения isql. В приложении echo выведите select 1 и перенаправьте в isql, который затем будет подключаться с использованием секции asterisk-connector, добавленной в /etc/odbc.ini. Вы должны получить следующий результат (или по крайней мере что-то подобное; мы ищем результат 1 rows fetched):

$ echo “select 1” | isql -v asterisk-connector asterisk some_secret_password

+—————————————+

| Connected! |

| |

| sql-statement |

| help [tablename] |

| quit |

| |

+—————————————+

SQL>

+————+

| ?column? |

+————+

| 1 |

+————+

SQLRowCount returns 1

1 rows fetched

Пароль, который вы использовали при создании пользователя ‘asterisk’@ в разделе “Настройка MySQL“.

Теперь, когда unixODBC установлен, настроен и проверен на работоспособность, вам необходимо перекомпилировать Asterisk, чтобы модули ODBC были созданы и установлены. Вернитесь в исходный каталог Asterisk и запустите скрипт ./configure, чтобы он знал, что вы установили unixODBC:

$ cd ~ / src / asterisk-complete / asterisk / 11

$ ./configure

$ make menuselect

$ make install

Почти все в этой главе включено по умолчанию. Вы должны запустить make menuselect, чтобы убедиться, что модули, связанные с ODBC, включены. К ним относятся cdr_odbc, cdr_adaptive_odbc, func_odbc, func_realtime, pbx_realtime, res_config_odbc и res_odbc. Для голосовой почты, хранящейся в базе данных ODBC, обязательно выберите ODBC_STORAGE в меню Voicemail Build Options (Параметры сборки голосовой почты). Вы можете проверить, что модули существуют в каталоге /usr/lib/asterisk/modules.

Как только Linux сможет использовать ODBC, Asterisk также сможет использовать ODBC. Суть в том, что ваш ODBC должен работать под Linux без ошибок, прежде чем вы погрузитесь в часть процесса настройки Asterisk.

Компиляция модулей ODBC для Asterisk

Теперь, когда ODBC установлен и протестирован, вам необходимо скомпилировать соответствующие модули для Asterisk.

Самый простой способ сделать это – просто вернуться в каталог установки («Asterisk»), повторно запустить sudo ./configure, а затем sudo make install и система распознает что зависимости для ODBC теперь выполнены и автоматически скомпилирует и установит соответствующие модули для Asterisk. Если хотите, то можете запустить make menuselect и убедиться, что функция диалплана func_odbc теперь отображается как [*] func_odbc, а модуль ресурсов res_odbc также указан как [*] res_odbc.

Настройка res_odbc для подключения Asterisk к ODBC

Подключения Asterisk к ODBC настраиваются в файле res_odbc.conf, расположенном в /etc/asterisk. В res_odbc.conf устанавливаются параметры, которые различные модули Asterisk будут использовать для подключения к базе данных.

Опции pooling и limit являются весьма полезными для баз данных MS SQL и Sybase. Они позволяют устанавливать несколько соединений (вплоть до limit подключений) с базой данных, гарантируя при этом, что каждое соединение имеет только одну инструкцию, выполняемую одновременно (это связано с ограничением в протоколе, используемом этими серверами баз данных).

Измените res_odbc.conf следующим образом:

[asterisk]

enabled => yes

dsn => asterisk-connector

username => asterisk

password => welcome

pooling => no

limit => 1

pre-connect => yes

Параметр dsn указывает на подключение к базе данных, которое вы настроили в /etc/odbc.ini, а параметр pre-connect сообщает Asterisk открыть и поддерживать соединение с базой данных при загрузке модуля res_odbc.so. Это снижает некоторые издержки, связанные с многократной установкой и разрывом соединения с базой данных.

После того как вы настроили res_odbc.conf, запустите Asterisk и проверьте соединение с базой данных с командой CLI odbc show:

*CLI> odbc show

ODBC DSN Settings

—————–

Name: asterisk

DSN: asterisk-connector

Last connection attempt: 1969-12-31 19:00:00

Pooled: No

Connected: Yes

Управление базами данных

Хотя в этой книге не описывается управление базами данных, по крайней мере стоит кратко отметить некоторые приложения, которые можно использовать для управления базами данных. Некоторые из них существуют, некоторые из них являются локальными клиентскими приложениями, запущенными с Вашего компьютера и подключенными к базе данных, а другие – веб-приложениями, которые могут обслуживаться с того же компьютера, на котором запущена сама база данных, что позволяет подключаться удаленно.

Некоторые из тех, которые мы использовали, включают в себя:

Устранение неполадок с базой данных

При работе с соединениями базы данных ODBC и Asterisk важно помнить, что соединение ODBC абстрагирует часть информации, переданной между Asterisk и базой данных. В случаях, когда все работает не так, как ожидалось, вам может потребоваться включить логирование на вашей платформе базы данных, чтобы узнать, что Asterisk отправляет в базу (например, какие инструкции SELECT, INSERT или UPDATE запускаются из Asterisk), что база данных видит и почему может отклонять инструкции.

Например, одной из наиболее распространенных проблем, возникающих при интеграции базы данных ODBC, является некорректно определенная таблица или отсутствующий столбец, существование которого ожидает Asterisk. Хотя большие успехи были сделаны в виде адаптивных модулей, не все части Asterisk являются адаптивными. В случае хранилища голосовой почты ODBC вы, возможно, пропустили столбец, такой как flag, который является новым столбцом, не найденным в версиях Asterisk до 11.4 Чтобы выяснить, почему ваши данные не записываются в базу данных, как ожидалось, вы должны включить логирование операторов на стороне базы данных, а затем определить, какая инструкция выполняется и почему база данных отклоняет ее.

Инъекция SQL

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

В случае с Asterisk вы должны подумать о том, какие ввходные данные принимаете от пользователей (как правило то, что они могут отправить в диалплан), и работать над дезинфекцией этих данных, чтобы убедиться, что вы разрешаете только символы, которые допустимы для вашего приложения. Например, типичный телефонный вызов допускал бы только цифры в качестве ввода (и, возможно, символы * и #), поэтому не было бы причин принимать какие-либо другие символы. Имейте в виду, что протокол SIP допускает больше, чем просто числа как часть адреса, поэтому не предполагайте, что кто-то, пытающийся скомпрометировать вашу систему, ограничится одними цифрами.

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

Мощь вашего диалплана с func_odbc

Функция диалплана func_odbc, возможно крутейшая5 и самая мощная функция диалплана в Asterisk. Она позволяет вам определять и использовать относительно простые функции в вашем диалплане, которые будут извлекать информацию из баз данных по мере обработки вызовов. Существуют всевозможные способы их использования, такие как управление пользователями или возможность совместного использования динамической информации в кластере машин Asterisk. Мы не будем утверждать, что это упростит проектирование и создание кода диалплана, но мы пообещаем, что это позволит вам добавить совершенно новый уровень мощности в ваш диалплан, особенно если вы любите работать с базами данных. До сих пор мы не можем думать о тех, кто не любит func_odbc.

func_odbc позволяет определять SQL-запросы, которыми назначаются имена функций. По сути, вы создаете пользовательские функции, которые получают результаты, выполняя запросы к базе данных. В func_odbc.conf вы указываете отношения между именами функций, которые вы создаете, и операторами SQL, которые хотите выполнить. Обращаясь к названным функциям в диалплане, вы можете получать и обновлять значения в базе данных.

Чтобы вы были в хорошем настроении для дальнейшего, мы хотим, чтобы вы представили себе сэндвич Dagwood.6

Можете ли вы передать общий опыт такой вещи, показывая кому-то изображение помидора или размахивая кусочком сыра? Вряд ли. Это та головоломка, с которой мы столкнулись, когда пытались дать полезные примеры того, почему func_odbc настолько силен. Итак, мы решили создать целый сэндвич для вас. Это довольно полный рот, но после нескольких укусов это арахисовое масло и желе никогда не будут одинаковыми.

Отношения файлов конфигурации ODBC

Чтобы Asterisk мог использовать ODBC из диалплана, несколько файлов должны выстроиться в линию. Рисунок 16-1 пытается дать визуальное понимание этого. Вы, вероятно, найдете эту диаграмму более полезной, как только поработаете с примерами в следующих разделах.



Рисунок 16-1. Взаимоотношения между func_odbc.conf, res_odbc.conf, /etc/odbc.ini (unixODBC) и соединением с базой данных

Нежное введение в func_odbc.

Прежде чем мы углубимся в func_odbc, мы чувствуем, что история в порядке.

Самое первое использование func_odbc, которое случилось когда его автор еще писал его, также является хорошим введением в его использование. Клиент одного из авторов модуля отметил, что некоторые люди, вызывающие его коммутатор, нашли способ совершать бесплатные звонки с его системы. Хотя его конечной целью было изменить свой диалплан, чтобы избежать этих проблем, он нуждался в черном списке определенных номеров вызывающих абонентов, в то же время, он хотел использовать для этого базы данных сервера Microsoft SQL.

За некоторыми исключениями это фактический диалплан:

[span3pri]

exten => _50054XX,1,NoOp()

same => n,Set(CDR(accountcode)=pricall)

; Этот callerID присутствует в базе данных?

same => n,GotoIf($[${ODBC_ANIBLOCK(${CALLERID(number)})}]?busy)

same => n(dial),Dial(DAHDI/G1/${EXTEN})

same => n(busy),Busy(10) ; Да, вы находитесь в черном списке.

same => n,Hangup

Этот диалплан, в двух словах, передает все вызовы другой системе для целей маршрутизации, за исключением вызовов, чьи CallerID находятся в черном списке. Вызовы, входящие в эту систему, имеют блок из 100 семизначных идентификаторов DID. Вы заметите, что используется функция диалплана, которую вы не найдете ни в одной из функций, которые поставляются с Asterisk: ODBC_ANIBLOCK(). Эта функция была определена в другом файле конфигурации func_odbc.conf:

[ANIBLOCK]

dsn=telesys

readsql=SELECT IF(COUNT(1)>0, 1, 0) FROM Aniblock WHERE NUMBER=’${ARG1}’

Итак, ваша функция ODBC_ANIBLOCK()7 подключается к источнику данных в res_odbc.conf с именем telesys и выбирает количество записей, которые имеют NUMBER, указанный в аргументе, который (ссылаясь на наш диалплан выше) является CallerID. Номинально эта функция должна возвращать либо 1 (указывающий CallerID присутствует в таблице Aniblock), либо 0 (если это не так). Это значение также напрямую оценивается как true или false, что означает что нам не нужно использовать выражение в нашем диалплане для усложнения логики.

И это, в двух словах, о func_odbc: написание пользовательских функций диалплана, которые возвращают результат из базы данных. Далее, более подробный пример того, как можно использовать func_odbc.

Веселимся с func_odbc: горячий стол

Хорошо, вернемся к сэндвичу Dagwood, который мы обещали.

Мы полагаем, что значение func_odbc станет для вас очень ясным если вы выполните следующий пример, который приведет к созданию новой функции в вашей системе Asterisk, которая сильно зависит от func_ocbc.

Представьте себе небольшую компанию с отделом продаж из пяти человек, которым приходится делить два стола. Это не так жестоко, как кажется, потому что эти люди проводят большую часть своего времени в дороге и каждый из них находится в офисе не более одного дня в неделю.

Тем не менее, когда они попадают в офис, то хотели бы чтобы система знала, за каким столом они сидят, чтобы их звонки могли быть направлены туда. Кроме того, босс хочет иметь возможность отслеживать когда они находятся в офисе и контролировать привилегии вызова с этих телефонов, когда никого нет.

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

Давайте начнем с простых вещей и создадим два настольных телефона в файле sip.conf:

;sip.conf

;HOT DESK ТЕЛЕФОНЫ

[hot_desk_phone](!) ;шаблон

type = friend

host = dynamic

context = hotdesk

qualify = yes

[0000FFFF0003](hot_desk_phone) ; первый стол

secret = this_phone_needs_a_good_password

[0000FFFF0004] (hot_desk_phone) ; второй стол

secret = this_phone_also_needs_a_secret

; КОНЕЦ ТЕЛЕФОНОВ HOT DESK

Эти два настольных телефона входят в диалплан в контексте hotdesk в extensions.conf, который мы вскоре определим. Если вы хотите, чтобы эти устройства действительно работали, вам, конечно же, нужно будет установить соответствующие параметры в самих устройствах, но мы это рассмотрели в Главе 5. Если вы создаете эти телефоны в своей лаборатории, имена устройств, которые мы используем, нужно заменить на то, что вам лучше всего подходит для ваших нужд (мы рекомендуем использовать MAC-адрес).

Это все для sip.conf. У нас есть два куска хлеба, которые пока не сэндвич.

Теперь давайте создадим часть базы данных (мы предполагаем, что у вас есть база данных ODBC, созданная и работающая, как описано в предыдущих частях этой главы). Сначала подключитесь к консоли базы данных.

Для PostgreSQL:

$ sudo su – postgres

$ psql -U asterisk -h localhost asterisk

Password:

Затем создайте таблицу со следующим битом SQL:

CREATE TABLE ast_hotdesk

(

id serial NOT NULL,

extension int8,

first_name text,

last_name text,

cid_name text,

cid_number varchar(10),

pin int4,

context text,

status bool DEFAULT false,

“location” text,

CONSTRAINT ast_hotdesk_id_pk PRIMARY KEY (id)

)

WITHOUT OIDS;

Для MySQL:

$ mysql -u asterisk -p asterisk

Enter password:

Затем создайте таблицу со следующим битом SQL:

CREATE TABLE ast_hotdesk

(

id serial NOT NULL,

extension int8,

first_name text,

last_name text,

cid_name text,

cid_number varchar(10),

pin int4,

context text,

status bool DEFAULT false,

location text,

CONSTRAINT ast_hotdesk_id_pk PRIMARY KEY (id)

);

Информация о таблице приведена в Таблице 16-1.

Таблица 16-1. Резюме таблицы ast_hotdesk

Имя столбца Тип столбца
id серийный, автоувеличивающийся
extension целочисленный
first_name текст
last_name текст
cid_name текст
cid_number 10-символьная переменная
pin целочисленная
context текст
status логическая, по-умолчанию false
location текст

После этого заполните базу данных следующей информацией (некоторые из значений, которые вы увидите, на самом деле изменятся только после выполнения работы диалплана, но мы включим их здесь в качестве примера).

На консоли PostgreSQL запустите следующие команды:

asterisk => INSERT INTO ast_hotdesk (‘extension’, ‘first_name’, ‘last_name’, \

‘cid_name’, ‘cid_number’, ‘pin’, ‘context’, ‘location’) \

VALUES (1101, ‘Leif’, ‘Madsen’, ‘Leif Madsen’, ‘4165551101’, ‘555’,\

‘longdistance’ , ‘0000FFFF0003’);

На консоли MySQL запустите следующие команды:

mysql> INSERT INTO ast_hotdesk (extension, first_name, last_name, cid_name,

cid_number, pin, context, status, location)

VALUES (1101, ‘Leif’, ‘Madsen’, ‘Leif Madsen’,

‘4165551101’, ‘555’, ‘longdistance’, 1, ‘0000FFFF0003’);

mysql> INSERT INTO ast_hotdesk (extension, first_name,

last_name, cid_name, cid_number, pin, context)

VALUES (1104,’Mark’,’Spencer’,’Mark Spencer’,

‘4165551104’,’558′,’international’);

Повторите эти команды, изменив значения VALUES по мере необходимости, для всех записей, которые вы хотите иметь в базе данных.8 После ввода данных образца вы можете просмотреть данные в таблице ast_hotdesk, запустив простой оператор SELECT из консоли базы данных:

mysql> SELECT * FROM ast_hotdesk;

которая может дать вам что-то вроде следующего вывода:

+—-+———–+————+————–+——————+

| id | extension | first_name | last_name | cid_name |

+—-+———–+————+————–+——————+

| 1 | 1101 | Leif | Madsen | Leif Madsen |

| 2 | 1104 | Mark | Spencer | Mark Spencer |

| 3 | 1105 | Matt | Jordan | Matt Jordan |

| 4 | 1102 | Jim | Van Meggelen | Jim Van Meggelen |

| 5 | 1103 | Russell | Bryant | Russell Bryant |

+—-+———–+————+————–+——————+
+————+——+—————+——–+————–+

| cid_number | pin | context | status | location |

+————+——+—————+——–+————–+

| 4165551101 | 555 | longdistance | 1 | 0000FFFF0003 |

| 4165551104 | 558 | international | 0 | NULL |

| 4165551105 | 559 | local | 0 | NULL |

| 4165551102 | 556 | longdistance | 0 | NULL |

| 4165551103 | 557 | local | 0 | NULL |

+————+——+—————+——–+————–+

У нас есть приправы, так что давайте перейдем к нашему диалплану. Здесь и будет происходить волшебство.

Где-то в extensions.conf нам необходимо создать контекст hotdesk. Для начала давайте определим внутерний номер-шаблон, который позволит пользователям входить в систему:

; extensions.conf

; Возможность горячего стола

[hotdesk]

; Вход в систему горячего стола

exten => _99110[1-5],1,NoOp()

same => n,Set(E=${EXTEN:2}) ; отделить ведущие 99

same => n,Verbose(1,Hot Desk Extension ${E} is changing status)

same => n,Verbose(1,Checking current status of extension ${E})

same => n,Set(${E}_STATUS=${HOTDESK_INFO(status,${E})})

same => n,Set(${E}_PIN=${HOTDESK_INFO(pin,${E})})

Мы еще не закончили писать этот код, но нам нужно отойти на несколько страниц, чтобы обсудить, где мы находимся до сих пор.

Когда торговый агент садится за стол, он регистрируется, набрав 99 плюс его собственный добавочный номер. В этом случае мы разрешили номерам с 1101 по 1105 войти в систему совпадением шаблонов _99110[1-5]. Вы можете так же легко сделать это менее ограничительным, используя _9911XX (с 1100 по 1199). Это расширение использует func_odbc для выполнения поиска с помощью функции диалплана HOTDESK_INFO(). Эта настраиваемая функция (которую мы определим в файле func_odbc.conf) выполняет оператор SQL и возвращает все, что извлекается из базы данных.

Мы бы определили новую функцию HOTDESK_INFO() в func_odbc.conf следующим образом:

[INFO]

prefix=HOTDESK

dsn=asterisk

readsql=SELECT ${ARG1} FROM ast_hotdesk WHERE extension = ‘${ARG2}’

Это много всего за несколько строк. Давайте быстро прикроем их, прежде чем двигаться дальше.9

Прежде всего, prefix является необязательным (префикс по умолчанию – ‘ODBC’). Это означает, что если вы не определяете префикс, Asterisk добавляет ‘ODBC’ к имени функции (в данном случае INFO), что означает, что эта функция станет ODBC_INFO(). Это не очень хорошо описывает, что делает функция, поэтому может быть полезно назначить префикс, который помогает связать ваши функции ODBC с задачами, которые они выполняют. Мы выбрали ‘HOTDESK’, что означает, что эта настраиваемая функция будет называться HOTDESK_INFO() в диалплане.

Причина, по которой prefix отделен, заключается в том, что автор модуля хотел уменьшить возможные коллизии с существующими функциями диалплана. Цель префикса состояла в том, чтобы разрешить несколько копий одной и той же функции, подключенной к различным базам данных, для многозадачных систем Asterisk. Мы, авторы, были немного более либеральны в использовании префикса, чем первоначально предполагал разработчик.

Атрибут dsn указывает Asterisk какое соединение использовать от res_odbc.conf. Поскольку в базе данных можно настроить несколько подключений к базе данных res_odbc.conf, мы укажем какой из них следует использовать здесь. На Рисунке 16-1 мы показываем взаимосвязь между различными конфигурациями файлов и тем, как они ссылаются на цепочку для подключения к базе данных.

Файл func_odbc.conf.sample в исходниках Asterisk содержит дополнительную информацию о том, как работать с несколькими базами данных и контролировать чтение и запись информации для различных соединений DSN. В частности, аргументы readhandle, writehandle, readsql и writeql предоставят вам большую гибкость для интеграции и управления базами данных.

Наконец, мы определяем наш оператор SQL с атрибутом readsql. Функции диалплана можно вызывать в двух разных форматах: один для извлечения информации и один для настройки информации. Атрибут readsql используется при вызове функции HOTDESK_INFO() с форматом извлечения (мы могли бы выполнить отдельный оператор SQL с атрибутом writesql, мы обсудим формат для этого атрибута немного позже в этой главе).

Чтение значений из этой функции примет этот формат в диалплане:

exten => s,n,Set(RETURNED_VALUE=${HOTDESK_INFO(status,1101)})

Это вернет значение, находящееся в базе данных в столбце status, где столбец extension равен 1101. status и 1101, которые мы передаем функции HOTDESK_INFO() затем помещаются в оператор SQL, присвоенный атрибуту readsql, доступный как ${ARG1} и ${ARG2} соответственно. Если бы мы допустили третий вариант, он был бы доступен как ${ARG3}.

После выполнения инструкции SQL возвращаемое значение (если есть) назначается канальной переменной RETURNED_VALUE.

Использование функции ARRAY()

В нашем примере мы используем два отдельных вызова базы данных и присваиваем эти значения паре переменных канала, ${E}_STATUS и ${E}_PIN. Это было сделано чтобы упростить пример:

exten => _110 [1-5],1,NoOp()

same => n,Dial(${Е}_STATUS=${HOTDESK_INFO(status,${Е})})

same => n,Set(${E}_PIN=${HOTDESK_INFO(pin,${E})})

В качестве альтернативы мы могли бы вернуть несколько столбцов и сохранить их в отдельные переменные, используя функцию набора номера ARRAY(). Если бы мы определили наш оператор SQL в файле func_odbc.conf следующим образом:

readsql=SELECT pin,status FROM ast_hotdesk WHERE extension = ‘${E}’

мы могли бы использовать функцию ARRAY() для сохранения каждого столбца информации из строки в свою переменную с одним вызовом базы данных:

exten => _110[1-5],1,Set(ARRAY(${E}_PIN,${E}_STATUS)=${HOTDESK_INFO(${E})})

Использование ARRAY() удобно в любое время, когда вы можете получить значения, разделенные запятыми и хотите присвоить значения отдельным переменным, например CURL().

Итак, в первых двух строках следующего блока кода мы передаем значения status и значение, содержащееся в переменной ${E} (например, 1101), в функцию HOTDESK_INFO(). Эти два значения затем заменяются в операторе SQL с помощью ${ARG1} и ${ARG2} соответственно и выполняется инструкция SQL. Наконец, возвращаемое значение присваивается переменной канала ${E}_STATUS.

Итак, давайте закончим писать внутренний номер-шаблон:

same => n,Set(${E}_STATUS=${HOTDESK_INFO(status,${E})})

same => n,Set(${E}_PIN=${HOTDESK_INFO(pin,${E})})

same => n,GotoIf($[${ODBCROWS} < 0]?invalid_user,1)

same => n,GotoIf($[${${E}_STATUS} = 1]?logout,1:login,1)

После присвоения значения столбца status в переменную ${E}_STATUS (если пользователь идентифицирует сам себе как номер 1101, имя переменной будет 1101_STATUS), мы проверяем если получили значение из базы данных (проверка ошибок), используя переменную канала ${ODBCROWS}.

Последняя строка в блоке проверяет состояние телефона и, если агент в настоящий момент вошел в систему, отключает его. Если агент еще не вошел в систему, он перейдет на расширение login, с приоритетом 1 в том же контексте.

Помните, что в традиционной телефонной системе все расширения должны быть номерами, но в Asterisk расширения также могут иметь имена. Возможным преимуществом использования расширения, которое не является числом, является то, что пользователю будет намного сложнее набрать его со своего телефона и, следовательно, более безопасно. В этом примере мы будем использовать несколько именованных расширений. Если вы хотите быть абсолютно уверены, что злоумышленник не сможет получить доступ к этим именованным расширениям, просто используйте трюк, который использует загрузчик AEL: запустите с приоритетом, отличным от 1. Вы можете получить доступ к первой строке расширения присвоив ему метку приоритета и ссылаясь на нее с помощью комбинации имя расширения/метка приоритета.

Расширение login выполняет некоторые первоначальные проверки, чтобы проверить PIN-код, введенный агентом. (Кроме того, мы использовали функцию FILTER(), чтобы убедиться что были введены только цифры во избежание некоторых проблем с SQL-инъекциями.) Мы разрешаем ему три попытки на введение правильного PIN-кода, и если все попытки неудачны, то отправляем вызов на расширение login_fail (которое мы опишем позже):

exten => login,1,NoOp() ; установить начальные значения счетчика

same => n,Set(PIN_TRIES=1) ; счетчик попыток pin

same => n,Set(MAX_PIN_TRIES=3) ; установка макс числа попыток входа

same => n,Playback(silence/1) ; воспроизвести некоторое молчание, так

; что первая подсказка не отрезана

same => n(get_pin),NoOp()

same => n,Set(PIN_TRIES=$[${PIN_TRIES} + 1]) ; увеличение счетчика pin

same => n,Read(PIN_ENTERED,enter-password,${LEN(${${E}_PIN})})

same => n,Read(PIN_ENTERED=${FILTER(0-9,${PIN_ENTERED})})

same => n,GotoIf($[“${PIN_ENTERED}” = “${${E}_PIN}”]?valid_login,1)

same => n,Playback(pin-invalid)

same => n,GotoIf($[${PIN_TRIES} <= ${MAX_PIN_TRIES}]?get_pin:login_fail,1)

Если введен PIN-код, мы проверяем логин с расширением valid_login. Сначала используем переменную CHANNEL, чтобы выяснить, какое телефонное устройство вызывается агентом. Переменная CHANNEL обычно заполняется чем-то вроде SIP/0000FFFF0001-ab4034c, поэтому мы используем функцию CUT(), чтобы сначала убрать часть строки SIP/ и назначить ее LOCATION. Затем мы удаляем часть строки -ab4034c, отбрасываем ее и назначаем оставшуюся часть (0000FFFF0001) переменной LOCATION:

exten => valid_login,1,NoOp()

; отрезаем технологию канала и назначаем переменной LOCATION

same => n,Set(LOCATION=${CUT(CHANNEL,/,2)})

; отрезаем уникальный идентификатор и сохраняем остальное в переменной

; LOCATION

same => n,Set(LOCATION=${CUT(LOCATION,-,1)})

Мы используем еще одну пользовательскую функцию, созданную в файле func_odbc.conf – HOTDESK_CHECK_PHONE_LOGINS(), чтобы проверить, были ли ранее зарегистрированы другие пользователи под этим телефоном и просто забыли выйти из системы. Если количество неавторизованных пользователей больше 0 (оно никогда не должно быть больше 1, но мы всё равно проверяем на большие значения и сбрасываем их тоже), он запускает логику в расширении logout_login:

; func_odbc.conf

[CHECK_PHONE_LOGINS]

prefix=HOTDESK

dsn=asterisk

readsql=SELECT COUNT(status) FROM ast_hotdesk WHERE status = ‘1’

readsql+=AND location = ‘${ARG1}’

Из-за ограничений длины строк в книге мы разбили команду readsql на несколько строк, используя синтаксис +=, который сообщает Asterisk добавлять содержимое после readsql+= к последнему определенному значению readsql=. Использование += применимо не только к параметру readsql, но также может использоваться и в других местах в других .conf-файлах в Asterisk. Например, что-то вроде callerid+= можно использовать в sip.conf.

Если нет других агентов вошедших в устройство, мы обновляем состояние входа для данного пользователя функцией HOTDESK_STATUS():

; Продолжение расширения valid_login ниже

same => n,Set(USERS_LOGGED_IN=${HOTDESK_CHECK_PHONE_LOGINS(${LOCATION})})

same => n,GotoIf($[${USERS_LOGGED_IN} > 0]?logout_login,1)

same => n(set_login_status),NoOp()

; Устанавливаем состояние для телефона в ‘1’ и где агент вошел в систему

same => n,Set(HOTDESK_STATUS(${E})=1,${LOCATION})

same => n,GotoIf($[${ODBCROWS} < 1]?error,1)

same => n,Playback(agent-loginok)

same => n,Hangup()

Мы создаем функцию записи в func_odbc.conf следующим образом:

[STATUS]

prefix=HOTDESK

dsn=asterisk

writeql=UPDATE ast_hotdesk SET status = ‘${SQL_ESC(${VAL1})}’, writeql+= location=’${SQL_ESC(${VAL2})}’

writeql+=WHERE extension = ‘${SQL_ESC(${ARG1})}

Синтаксис очень похож на readsql, рассмотренный ранее в этой главе, но здесь есть несколько новых вещей, поэтому давайте обсудим их, прежде чем двигаться дальше.

Первое что вы, возможно, заметили, это то, что теперь мы имеем переменные ${VALx} и ${ARGx} в нашем операторе SQL.

Мы также обернули значения ${VALx} и ${ARGx} в функцию SQL_ESC(), которая будет экранировать символы, такие как обратные кавычки, которые могут быть использованы при атаке SQL-инъекции.

Они содержат значения, которые мы передаем функции из диалплана. В этом случае у нас есть две переменные VAL и одна ARG, которые были установлены из диалплана через этот оператор:

Set(HOTDESK_STATUS(${E})=1,${LOCATION})

Обратите внимание, что синтаксис немного отличается от синтаксиса функции в стиле чтения. Это сигнализирует Asterisk, что вы хотите выполнить запись (это тот же синтаксис, что и для других функций диалплана).

Мы передаем значение переменной ${E} функции HOTDESK_STATUS(), значение которой затем доступно в операторе SQL в func_odbc.conf с переменной ${ARG1}. Затем мы передаем два значения: 1 и ${LOCATION}. Они доступны оператору SQL в переменных ${VAL1} и ${VAL2} соответственно.

Использование SQL непосредственно в диалплане.

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

Теоретически вы можете создать только одну функцию в func_odbc.conf следующим образом:

[SQL]

prefix=GENERIC

dsn=asterisk

readsql=${SQL_ESC(${ARG1})}

writeql=${SQL_ESC(${VAL1})}

Тогда, в диалплане вы могли бы написать практически любой тип SQL, который захотели (при условии, что ODBC-коннектор мог бы обрабатывать его, что не имеет ничего общего с Asterisk). Затем эта функция должна передать любую строку, указанную вами, в базу данных через ODBC-коннектор.1

Некоторые утверждают, что это приводит к большей детализации в вашем диалплане; другие будут настаивать на том, что полезно иметь гораздо более простой файл func_odbc.conf:

[odbc_hacking]

exten => 8811,1,Goto(odbcreadtest,1)

exten => 8822,1,Goto(odbcwritetest,1)

exten => odbcreadtest,1,Noop()

same => n,Set(result=${GENERIC_SQL(SELECT first_name

FROM ast_hotdesk WHERE id=4)})

same => n,Verbose(1,${result})

same => n,Hangup()

; обратите внимание на необходимость экранирования символов кавычек

exten => odbcwritetest,1,Noop()

same => n,Set(GENERIC_SSQL()

= UPDATE ast_hotdesk SET

first_name=\’${EXTEN}\’WHERE id=4)

same => n,Verbose (1,ODBC_RESULT – ${OBDBC_RESULT})

same => n,Hangup()

Мы склонны думать, что обычно лучше строить функции с помощью func_odbc.conf, для обработки запросов, которые вы будете выполнять из вашего диалплана; однако нельзя отрицать соблазна использовать одну функцию для обработки всех SQL-запросов и написать весь запрос непосредственно в диалплане. Попробуйте оба метода и посмотрите, что работает для вас (вы даже можете использовать комбинацию обоих типов).

Суть в том, что func_odbc – это очень гибкий модуль Asterisk, который является немалой частью того, почему мы его любим.

Как уже упоминалось ранее, если нам нужно было выйти из одного или нескольких агентов перед входом в этот, мы проверили бы это в расширении logout_login. Эта логика диалплана будет использовать функцию ODBC_FETCH(), чтобы вывести информацию из стека, возвращаемого функцией HOTDESK_LOGGED_IN_USER(). Скорее всего, она выполнит только один цикл, но это хороший пример того, как вы можете обновлять или анализировать несколько строк в базе данных.11

Первая часть нашего диалплана возвращает идентификационный номер, который мы можем использовать с функцией ODBC_FETCH(), для перебора возвращаемых значений. Мы напишем раздел диалплана, чтобы присвоить этот идентификатор переменной канала LOGGED_IN_ID:

same => n,Set(LOGGED_IN_ID=${HOTDESK_LOGGED_IN_USER(${LOCATION})})

Вот расширение logout_login, которое потенциально может циклически проходить через несколько строк:

exten => logout_login,1,NoOp()

; установите для всех пользователей, вошедших в систему на этом устройстве,

; состояние выхода из системы

same => n,Set(LOGGED_IN_ID=${HOTDESK_LOGGED_IN_USER(${LOCATION})})

same => n(start_loop),NoOp()

same => n,Set(WHO=${ODBC_FETCH(${LOGGED_IN_ID})})

same => n,GotoIf($[“${ODBC_FETCH_STATUS}” = “FAILURE”]?cleanup)

same => n,Set(HOTDESK_STATUS(${WHO})=0) ; выйти из телефона

same => n,Goto(start_loop)

same => n(cleanup),ODBCFinish(${LOGGED_IN_ID})

same => n,Goto(valid_login,set_login_status) ; возврат ко входу в систему

И вот функция, которую мы добавили бы в func_odbc.conf (не забудьте перезагрузить модуль func_odbc.so):

[LOGGED_IN_USER]

prefix=HOTDESK

dsn=asterisk

mode=multirow

readsql=SELECT extension FROM ast_hotdesk

readsql+= WHERE status = ‘1’

readsql+= AND location = ‘${SQL_ESC(${ARG1})}’

Мы назначаем первое значение, возвращаемое из базы данных (например, номер 1101) в канал WHO. Прежде чем что-либо делать, мы проверяем, была ли функция ODBC_FETCH() успешной при возврате данных. Если переменная канала ODBC_FETCH_STATUS содержит FAILURE, у нас нет данных для работы, поэтому мы переходим к метке приоритета cleanup.

Если у нас есть данные, мы передаем значение ${WHO} в качестве аргумента функции HOTDESK_STATUS(), которая содержит значение 0. Это первое переданное значение в HOTDESK_STATUS() и отображается как ${VAL1} в func_odbc.conf, где объявлена функция.

Если вы посмотрите на функцию HOTDESK_STATUS() в func_odbc.conf, вы увидите, что мы также можем передать второе значение, но не делаем этого, поскольку хотим удалить любые значения из этого столбца, чтобы вывести пользователя из системы, который не создает никакой ценности.

После использования HOTDESK_STATUS() для выхода пользователя из системы, мы возвращаемся к метке приоритета start_loop чтобы выполнить цикл по всем значениям, который просто выполняет NoOp(). После попытки получить значение, мы снова проверяем ODBC_FETCH_STATUS на FAILURE. Если это значение найдено, мы переходим на метку приоритета cleanup, где выполняем приложение диалплана ODBCFinish() для выполнения очистки. Затем возвращаемся к расширению valid_login на метку приоритета set_login_status.

Мультистроковая функциональность с func_odbc

Существует режим, позволяющий Asterisk обрабатывать несколько строк данных, возвращаемых из базы данных. Например, если бы нам нужно было создать функцию диалплана в func_odbc.conf, которая вернула бы все доступные внутренние номера, нам нужно было бы включить мультивывод для этой функции. Это приведет к тому, что функция будет работать несколько иначе, возвращая идентификационный номер, который затем может быть передан функции ODBC_FETCH(), чтобы возвращать каждую строку по очереди.

Давным-давно нам нужно было использовать функции SQL LIMIT и OFFSET для управления данными, возвращаемыми в Asterisk для итерации. Это было ресурсоемким (по крайней мере, в отношении многострочного режима), поскольку для каждой строки требовалось несколько запросов к базе данных.

Далее следует простой пример. Предположим, что у нас есть следующий func_odbc.conf:

[ALL_AVAIL_EXTENS]

prefix=GET

dsn=asterisk-connector

mode=multirow

readsql=SELECT extension FROM ast_hotdesk WHERE status = ‘${ARG1}’

и диалплан в extensions.conf, который выглядит примерно так:

[multirow_example]

exten => start,1,Verbose(1,Looping example)

same => n,Set(ODBC_ID=${GET_ALL_AVAIL_EXTENS(1)})

same => n,GotoIf($[${ODBCROWS} < 1]?no_rows,1)

same => n,Set(COUNTER=1)

same => n,While($[${COUNTER} <= ${ODBCROWS}])

same => n,Set(AVAIL_EXTEN_${COUNTER}=${ODBC_FETCH(${ODBC_ID})})

same => n,Set(COUNTER=$[${COUNTER + 1])

same => n,EndWhile()

same => n,ODBCFinish()

exten => no_rows,1,Verbose(1,No rows returned)

same => n,Playback(silence/1&invalid)

same => n,Hangup()

Функция ODBC_FETCH() по существу будет обрабатывать информацию как стек и каждый вызов ее с переданным ODBC_ID будет выводить следующую строку информации из стека. У нас также есть возможность использовать канальную переменную ODBC_FETCH_STATUS, которая устанавливается после функции ODBC_FETCH() (которая возвращает SUCCESS, если доступны дополнительные строки или FAILURE, если нет дополнительных строк). Это позволяет вам написать диалплан как показано ниже, который не использует счетчик, но по-прежнему циклически проходится по данным. Это может быть полезно, если мы ищем что-то конкретное и нет надобности просматривать все данные. Как только мы закончим, необходимо вызвать приложение ODBCFinish() для очистки всех оставшихся данных.

Вот еще один пример extensions.conf:

[multirow_example_2]

exten => start,1,Verbose(1,Looping example with break)

same => n,Set(ODBC_ID=${GET_ALL_AVAIL_EXTENS(1)})

same => n(loop_start),NoOp()

same => n,Set(ROW_RESULT=${ODBC_FETCH(${ODBC_ID})})

same => n,GotoIf($[“${ODBC_FETCH_STATUS}” = “FAILURE”]?cleanup,1)

same => n,GotoIf($[“${ROW_RESULT}” = “1104”]?good_exten,1)

same => n,Goto(loop_start)

exten => cleanup,1,Verbose(1,Cleaning up after all iterations)

same => n,Verbose(1,We did not find the extension we wanted)

same => n,ODBCFinish(${ODBC_ID})

same => n,Hangup()

exten => good_exten,1,Verbose(1,Extension we want is available)

same => n,ODBCFinish(${ODBC_ID})

same => n,Verbose(1,Perform some action we wanted)

same => n,Hangup()

Остальная часть контекста должна быть довольно простой (если некоторые из них не имеют смысла, мы предлагаем вам вернуться и обновить память в Главах 6 и 10). Единственным трюком, с которым вы не знакомы, может быть использование переменной канала ${ODBCROWS}, которая задается функцией HOTDESK_STATUS(). Она говорит нам сколько строк было затронуто в SQL UPDATE, которуе мы считаем равной 1. Если значение ${ODBCROWS} меньше 1, мы принимаем ошибку и обрабатываем ее соответствующим образом:

exten => logout,1,NoOp()

same => n,Set(HOTDESK_STATUS(${E})=0)

same => n,GotoIf($[${ODBCROWS} < 1]?error,1)

same => n,Playback(silence/1&agent-loggedoff)

same => n,Hangup()

exten => login_fail,1,NoOp()

same => n,Playback(silence/1&login-fail)

same => n,Hangup()

exten => error,1,NoOp()

same => n,Playback(silence/1&connection-failed)

same => n,Hangup()

exten => invalid_user,1,NoOp()

same => n,Verbose(1,Hot Desk extension ${E} does not exist)

same => n,Playback(silence/2&invalid)

same => n,Hangup()

Мы также включаем контекст hotdesk_outbound, который будет обрабатывать наши исходящие вызовы после того, как мы зарегистрировали агента в системе:

include => hotdesk_outbound

В контексте hotdesk_outbound используются многие из тех же принципов, которые уже обсуждались. В этом контексте используется сопоставление шаблонов, чтобы уловить любые номера, набранные с телефонов с поддержкой «горячего стола». Сначала мы устанавливаем переменную LOCATION с использованием переменной CHANNEL, затем определяем, какой внутренний номер (агент) заносится в систему и присваиваем это значение переменной WHO. Если эта переменная равна NULL мы отклоняем исходящий вызов. Если это не NULL мы получаем информацию об агенте с помощью функции HOTDESK_INFO() и присваем ее нескольким переменным CHANNEL, включая контекст для обработки вызова, где мы выполняем Goto() в контекст, который назначили (который управляет нашим исходящим вызовом).

Мы будем использовать функцию диалплана HOTDESK_PHONE_STATUS(), которую вы можете определить в func_odbc.conf следующим образом:

[PHONE_STATUS]

prefix=HOTDESK

dsn=asterisk

readsql=SELECT extension FROM ast_hotdesk WHERE status = ‘1’

readsql+= AND location = ‘${SQL_ESC(${ARG1})}’

Если мы попытаемся набрать номер, который не обрабатывается нашим контекстом (или одним из транзитных контекстов, т.е. международный включает междугородний, который содержит локальный), будет выполнено встроенное расширение i, которое воспроизводит сообщение о том, что действие не может быть выполнено, и завершает вызов:

[hotdesk_outbound]

exten => _X.,1,NoOp()

same => n,Set(LOCATION=${CUT(CHANNEL,/,2)})

same => n,Set(LOCATION=${CUT(LOCATION,-,1)})

same => n,Set(WHO=${HOTDESK_PHONE_STATUS(${LOCATION})})

same => n,GotoIf($[${ISNULL(${WHO})}]?no_outgoing,1)

same => n,Set(${WHO}_CID_NAME=${HOTDESK_INFO(cid_name,${WHO})})

same => n,Set(${WHO}_CID_NUMBER=${HOTDESK_INFO(cid_number,${WHO})})

same => n,Set(${WHO}_CONTEXT=${HOTDESK_INFO(context,${WHO})})

same => n,Goto(${${WHO}_CONTEXT},${EXTEN},1)

[international] ; как набирается из NANP

exten => _011.,1,NoOp()

same => n,Set(E=${EXTEN})

same => n,Goto(outgoing,call,1)

exten => i,1,NoOp()

same => n,Playback(silence/2&sorry-cant-let-you-do-that2)

same => n,Hangup()

include => longdistance

[longdistance] ; содержит NANP

exten => _1NXXNXXXXXX,1,NoOp()

same => n,Set(E=${EXTEN})

same => n,Goto(outgoing,call,1)

exten => _NXXNXXXXXX,1,Goto(1${EXTEN},1)

exten => i,1,NoOp()

same => n,Playback(silence/2&sorry-cant-let-you-do-that2)

same => n,Hangup()

include => local

[local] ; в пределах NANP NPA 416

exten => _416NXXXXXX,1,NoOp()

same => n,Set(E=${EXTEN})

same => n,Goto(outgoing,call,1)

exten => i,1,NoOp()

same => n,Playback(silence/2&sorry-cant-let-you-do-that2)

same => n,Hangup()

Приведенный выше пример не является полным диалпланом, а скорее демонстрирует концепцию того, как направить пользователей через различные части диалплана на основе информации, хранящейся в базе данных. Мы использовали очень упрощенные совпадения шаблонов здесь, и в продакшене вам, скорее всего, понадобится более подробный диалплан. В качестве примера, фактический NPA 416 содержит наложение (647), а также есть обмены в пределах NPA 905/289, которые обычно набираемы как местные NPA 416. Междугородние ограничения маршрутизации могут быть сложными.

Если вызов разрешен для выполнения, он отправляется в контекст [outgoing] для обработки, а имя и номер идентификатора абонента задаются с помощью функции CALLERID(). Затем вызов отправляется через канал SIP с помощью service_provider, который мы создали в файле sip.conf:

[outgoing]

exten => call,1,NoOp()

same => n,Set(CALLERID(name)=${${WHO}_CID_NAME})

same => n,Set(CALLERID(number)=${${WHO}_CID_NUMBER})

same => n,Dial(SIP/service_provider/${E})

same => n,Playback(silence/2&pls-try-call-later)

same => n,Hangup()

Наш service_provider может выглядеть в sip.conf примерно так:

[service_provider]

type=friend

host=switch1.service_provider.net

defaultuser=my_username

fromuser=my_username

secret=welcome

context=incoming

canreinvite=no

disallow=all

allow=ulaw

Теперь, когда мы внедрили довольно сложную функцию в диалплан с помощью func_odbc для извлечения и хранения данных в удаленной реляционной базе данных, вы можете начать видеть, что с помощью нескольких самоопределяющихся функций диалплана в файле func_odbc.conf и нескольких таблиц в базе данных вы можете создать несколько мощных приложений!

Сколько вещей вы только что придумали, к которым можно применить func_odbc?

Использование Realtime

Архитектура Asterisk Realtime12 (ARA) позволяет хранить все параметры, обычно хранящиеся в файлах конфигурации Asterisk (обычно в каталоге /etc/asterisk) в базе данных. Существует два типа Realtime: статический и динамический.

Статическая версия похожа на традиционный метод чтения файла конфигурации (информация загружается только при запуске из CLI), за исключением того, что данные считываются из базы данных.13

Динамический метод Realtime, который загружает и обновляет информацию, когда она используется живой системой, обычно используется для таких вещей, как пользователи и пиры SIP (или IAX2 и т.д.), а также ящики голосовой почты.

Для внесения изменений в статическую информацию требуется перезагрузка, как если бы вы изменили текстовый файл в системе, но динамическая информация была опробована Asterisk по мере необходимости, поэтому при внесении изменений в эти данные не требуется перезагрузка. Realtime настроен в extconfig.conf, расположенный в каталоге /etc/asterisk. Этот файл сообщает Asterisk, что загружать из базы данных и откуда, позволяя загружать определенные файлы из базы данных и другие файлы из стандартных файлов конфигурации.

Другой (возможно более старый) способ хранения конфигурации Asterisk состоял в использовании внешнего скрипта, который бы взаимодействовал с базой данных и генерировал соответствующие плоские файлы (или .conf файлы), а затем перезагружал соответствующий модуль после записи нового файла. У него есть преимущество (если база данных падает, ваша система будет продолжать функционировать, скрипт просто не будет обновлять файлы до восстановления связи с базой данных), но она также имеет недостатки. Одним из главных недостатков является то, что любые изменения, внесенные пользователем, не будут доступны до запуска скрипта обновления. Это, вероятно, не самая большая проблема для небольших систем, но в больших системах ожидания вступления изменений в силу могут вызвать проблемы, такие как приостановка вызова во время загрузки и анализа большого файла.

Некоторые из них можно устранить, используя реплицированную систему баз данных. Asterisk предоставляет возможность перехода на другую систему баз данных. Таким образом, вы можете объединять базы данных, используя связь master-master (для PostgreSQL, pgcluster, или Postgre-R;14 для MySQL он родной15) или master-slave (для PostgreSQL или Slony-I; для MySQL он родной) систему репликации.

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

Статический Realtime

Статическая конфигурация Realtime полезна, когда вы хотите загрузить из базы данных конфигурацию, которую обычно размещаете в файлах конфигурации в /etc/asterisk. Те же правила, которые применяются к файлам конфигурации в вашей системе, по-прежнему применяются при использовании Static Realtime. Например, после внесения изменений в конфигурацию вы должны либо запустить глобальную команду reload из Asterisk CLI, либо перезагрузить определенный модуль, связанный с файлом конфигурации (например, module reload chan_sip.so).

При использовании Static Realtime, мы говорим Asterisk какие файлы мы хотим загрузить из базы данных используя следующий синтаксис в файле extconfig.conf:

; /etc/asterisk/extconfig.conf

[settings]

filename.conf => driver,database[,table]

Нет файла конфигурации с именем filename.conf. Вместо этого используйте фактическое имя файла конфигурации, хранящегося в базе данных. Если имя таблицы не указано, Asterisk будет использовать имя файла в качестве имени таблицы (за исключением части .conf). Кроме того, все настройки внутри файла extconfig.conf должны находиться под заголовком [settings]. Имейте в виду, что вы не можете загружать определенные файлы из RealTime, включая asterisk.conf, extconfig.conf и logger.conf.

Модуль Static Realtime использует специально определенную форматированную таблицу, позволяющую Asterisk читать различные статические файлы из базы данных. Таблица 16-2иллюстрирует столбцы, поскольку они должны быть определены в вашей базе данных:

Таблица 16-2. Раскладка таблицы и описание ast_config

Имя столбца Тип столбца Описание
id серийный, автоувеличивающийся Автоувеличивающееся уникальное значение для каждой строки в таблице.
cat_metric целочисленный Вес категории в файле. Более низкая метрика означает что она отображается выше в файле (см. врезку Слово о метриках).
ver_metric целочисленный Вес предмета в категории. Более низкий показатель означает, что он отображается выше в списке (см. врезку Слово о метриках). Это полезно для таких вещей, как порядок кодеков в sip.conf или iax.conf, где вы хотите, чтобы сначала отображалось disallow = all (метрика 0), затем allow = ulaw (метрика 1), затем allow = gsm (метрика 2).
filename символьный (до 128) Имя файла, которое модуль обычно считывает с жесткого диска вашей системы (например, musiconhold.conf, sip.conf, iax.conf).
category символьный (до 128) Имя раздела в файле, например, [general]. Не заключайте имя в квадратные скобки при сохранении в базу данных.
var_name символьный (до 128) Опция слева от знака равенства (например, disallow – это var_name в disallow=all).
var_val символьный (до 128) Значение опции справа от знака равенства (например, all – это var_val в disallow=all).
commented целочисленный Любое значение, отличное от 0, будет оцениваться так, как если бы оно было с префиксом точки с запятой в плоском файле (закомментировано).

Слово о метриках

Метрики в статическом Realtime используются для управления порядком считывания объектов в память. Считайте cat_metric и var_metric исходными номерами строк в файле конфигурации. Сначала обрабатывается более высокое значение cat_metric, поскольку Asterisk сопоставляет категории снизу вверх. Внутри категории сначала обрабатывается более низкая переменная var_metric, поскольку Asterisk обрабатывает параметры снизу вверх (например, disallow=all должно быть установлено значение, меньшее, чем значение allow в категории, чтобы убедиться, что оно обработано первым ).

Простым файлом, который мы можем загрузить из Static Realtime, является файл musiconhold.conf.16 Начнем с перемещения этого файла во временное место:

$ cd /etc/asterisk

$ mv musiconhold.conf musiconhold.conf.old

Для того, чтобы классы были удалены из памяти, нам необходимо перезапустить Asterisk. Затем мы можем проверить, что наши классы пустые, запустив moh showclasses:

* CLI> core restart now

* CLI> moh show classes

* CLI>

OK, теперь войдите в свою базу данных и создайте следующую таблицу:

mysql> create table ast_config (

-> id int (8) primary key auto_increment,

-> cat_metric int (8),

-> var_metric int (8),

-> filename varchar (128),

-> category varchar (128),

-> var_name varchar (128),

-> var_val varchar (128),

-> commented int default 0

-> );

Давайте вставим класс [default] в Asterisk, но теперь мы загрузим его из базы данных. Подключитесь к своей базе данных и выполните следующие инструкции INSERT:

> INSERT INTO ast_config

(cat_metric,var_metric,filename,category,var_name,var_val)

VALUES

(1,1,’musiconhold.conf’,’default’,’mode’,’files’);

> INSERT INTO ast_config

(cat_metric,var_metric,имя_файла,category,var_name,var_val)

VALUES

(1,2,’musiconhold.conf’,’default’,’directory’,’/var/lib/asterisk/moh’);

Можно проверить, что значения попали в базу данных, выполнив инструкцию SELECT:

asterisk=# SELECT filename,category,var_name,var_val FROM ast_config;

filename | category | var_name | var_val

——————+—————-+————–+————————

musiconhold.conf | default | mode | files

musiconhold.conf | default | directory | /var/lib/asterisk/moh

(2 rows)

Есть еще одна модификация – необходимо изменить файл extconfig.conf в каталоге /etc/asterisk чтобы сообщить Asterisk получать данные для musiconhold.conf из базы данных, используя ODBC-коннектор. В первом столбце указано что мы используем драйверы ODBC для подключения (res_odbc.conf) и имя соединения asterisk (как определено в [asterisk] в res_odbc.conf). Добавьте следующую строку в конец файла extconfig.conf и затем сохраните его:

[settings]

musiconhold.conf => odbc,asterisk,ast_config

Затем подключитесь к консоли Asterisk и выполните перезагрузку:

* CLI> module reload res_musiconhold.so

Теперь вы можете проверить, загружается ли ваша музыка на удержание с базы данных, запустив moh showclasses:

*CLI> moh show classes

Class: general

Mode: files

Directory: /var/lib/asterisk/moh

И вот вы видите: musiconhold.conf загружен из базы данных. Если у вас есть проблемы с перезагрузкой модуля, загружающего данные в память, попробуйте перезапустить Asterisk. Вы можете выполнить те же действия для загрузки других файлов конфигурации из базы данных, если это необходимо.

Когда использовать preload в modules.conf для Realtime модулей

В modules.conf иногда вам может потребоваться загрузка модулей подключения к базам данных на ранней стадии с использованием директивы preload. Причина в том, что определенные файлы, такие как manager.conf, cdr.conf и rtp.conf, не являются внешними модулями, а скорее загружаются изнутри когда запускается Asterisk. Кроме того, если эти файлы, такие как manager.conf, хотят использовать именованные списки ACL из базы данных, вам также потребуется предзагрузка в данной ситуации.

Если бы мы использовали ODBC в качестве интерфейса базы данных в реальном времени, то нам нужно было бы добавить следующие строки в modules.conf сразу после опции autoload:

; modules.conf

[modules]

autoload=yes

preload => res_odbc.so

preload => res_odbc_config.so

Динамический Realtime

Система динамического Realtime используется для загрузки объектов, которые могут часто меняться, таких как SIP/IAX2 пользователи и пиры, очереди и их участники, а также сообщения голосовой почты.

Точно так же, когда новые записи, вероятно, будут добавляться на регулярной основе, мы можем использовать возможности базы данных чтобы позволить нам загружать эту информацию по мере необходимости.

Все для Realtime настроено в файле /etc/asterisk/extconfig.conf; однако динамический Realtime имеет явно определенные имена конфигурации. Все предопределенные имена должны быть настроены под заголовком [settings]. Например, определение пиров SIP выполняется в следующем формате:

; extconfig.conf

[settings]

sippeers => driver,database[,table]

Имя таблицы является необязательным. Если оно опущено, Asterisk будет использовать предопределенное имя (например sippeers), чтобы идентифицировать таблицу для поиска данных.

В нашем примере мы будем использовать таблицу ast_sippeers для хранения информации о SIP-пирах. Таким образом, чтобы настроить Asterisk для загрузки всех SIP-пиров из нашей базы данных в реальном времени, мы бы определили что-то вроде этого:

; extconfig.conf

[settings]

sippeers => odbc,asterisk,ast_sippeers

Предопределенные динамические имена для Realtime в extconfig.conf

Следующие имена предопределены для динамического Realtime:

  • iaxusers – IAX пользователи
  • iaxpeers – IAX пиры
  • sippeers – SIP пиры и пользователи
  • sipregs – регистрации SIP
  • voicemail – ящики голосовой почты
  • extensions – диалплан
  • meetme – MeetMe конференции
  • queues – очереди
  • queues_members – участники очередей
  • acls – списки доступа управления
  • musiconhold – музыка на удержание
  • queue_log – логирование очередей

Пользователи и пиры SIP загружаются из одной таблицы, определяемой помощью спекции sippeers из extconfig.conf. Это связано с тем, что будет поле type (точно так же, как если бы мы определяли тип в файле sip.conf), который позволит нам определить тип user, peer или friend. Если вы выгрузите chan_sip.so и затем загрузите его обратно в память (т.е. используя moduleunload chan_sip.so, а затем module load chan_sip.so) после настройки extconfig.conf, вас будут приветствовать некоторые предупреждения, сообщающие вам, какие столбцы вам не нужны для таблицы в Realtime. Если бы вы загружались из Realtime, то получили бы следующий аналогичный вывод в консоли (который был обрезан из-за требований к пространству):

WARNING: Realtime table ast_sippeers@asterisk requires column

‘name’, but that column does not exist!17

WARNING: Realtime table ast_sippeers@asterisk requires column

‘ipaddr’, but that column does not exist!

WARNING: Realtime table ast_sippeers@asterisk requires column

‘port’, but that column does not exist!

WARNING: Realtime table ast_sippeers@asterisk requires column

‘regseconds’, but that column does not exist!

WARNING: Realtime table ast_sippeers@asterisk requires column

‘defaultuser’, but that column does not exist!

WARNING: Realtime table ast_sippeers@asterisk requires column

‘fullcontact’, but that column does not exist!

WARNING: Realtime table ast_sippeers@asterisk requires column

‘regserver’, but that column does not exist!

WARNING: Realtime table ast_sippeers@asterisk requires column

‘useragent’, but that column does not exist!

WARNING: Realtime table ast_sippeers@asterisk requires column

‘lastms’, but that column does not exist!

Как вы можете видеть – нам не хватает нескольких столбцов из таблицы ast_sipfriends, которую мы определили для подключение к объекту asterisk, как определено в res_odbc.conf. Следующий шаг – создать нашу таблицу ast_sipfriends со всеми столбцами, перечисленными в предупреждающих сообщениях, в дополнение к следующим: столбец type, который требуется для определения пользователей, пиров и друзей; столбец secret, который используется для установки пароля; и столбец host, который позволяет нам определить, динамически ли регистрируется пир или имеет статический IP-адрес. Таблица 16-3 перечисляет все столбцы, которые должны отображаться в нашей таблице, и их типы.

Таблица 16-3. Минимальная realtime таблица sippeers/sipusers

Имя столбца Тип столбца
type символьный (до 6)
name символьный (до 128)
secret символьный (до 128)
context символьный (до 128)
host символьный (до 128)
ipaddr символьный (до 128)
port символьный (до 5)
regseconds больше-целочисленный
defaultuser символьный (до 128)
fullcontact символьный (до 128)
regserver символьный (до 128)
useragent символьный (до 128)
lastms целочисленный

Код SQL для создания этой таблицы должен выглядеть примерно так:

create table ast_sippeers

(

type varchar(6)

name varchar(128),

secret varchar(128),

context varchar(128),

host varchar(128),

ipaddr varchar(128),

port varchar(5),

regseconds bigint,

defaultuser varchar(128),

fullcontact varchar(128),

regserver varchar(128),

useragent varchar(128),

lastms integer

);

Для каждого пира, которого хотите зарегистрировать, нужно вставить данные в type, name, secret, context, host, и defaultuser. Остальные столбцы будут заполнены автоматически при регистрации пира.

Поля port, regseconds и ipaddr необходимы чтобы позволить Asrterisk хранить регистрационную информацию для пира, таким образом он может определить куда направлять вызовы. (Обратите внимание, что если пир static – вам нужно будет самостоятельно заполнить поле ipaddr.) Поле port является необязательным и по умолчанию используется стандартный порт, определенный в разделе [general], а regseconds останется пустым. В Таблице 16-4 перечислены некоторые примеры значений, которые мы будем использовать для заполнения нашей таблицы ast_sipfriends.

Таблица 16-4. Пример информации, используемой для заполнения таблицы ast_sipfriends:

Имя столбца Значение
type friend
name 0000FFFF0008
defaultuser 0000FFFF0008
host dynamic
secret dont-use-this-password-pick-another18
context LocalSets

Однако, перед регистрацией ваших пиров вы должны включить кэширование в режиме Realtime в sip.conf. В противном случае пир не будет загружен в память, и регистрация не будет запомнена. Если ваши пиры только совершают вызовы и не нуждаются в регистрации в вашей системе, вам не нужно включать кэширование в Realtime, потому что пиры будут проверяться по базе базы данных каждый раз, когда они совершают вызов. Однако, если вы загружаете своих пиров в память, с базой данных необходимо будет связаться только при первоначальной регистрации и по истечении срока регистрации.

Дополнительные параметры в sip.conf существуют для пиров в Realtime. Они определены в разделе [general] и описаны в Таблице 16-5.

Таблица 16-5. Параметры Realtime в sip.conf

Параметр конфигурации Описание
rtcachefriends Кэш пиров в памяти по мере необходимости после того, как они связались с сервером. То есть, при запуске Asterisk пиры не загружаются в память автоматически; только после того, как пир связался с сервером (например, через регистрацию или телефонный звонок) загружается в память. Значения yes или no.
rtsavesysname Когда пир регистрируется в системе, сохраняется systemname (как определено в asterisk.conf) в поле regserver в базе данных. (Дополнительные сведения см. в разделе “Установка имени системы для глобальных уникальных идентификаторов”.) Использование regserver полезно при наличии нескольких серверов, регистрирующих пиров в одной таблице. Значения yes или no.
rtupdate Отправляет регистрационную информацию, такую как IP-адрес, порт отправителя, период регистрации и имя пользователя user-agent в базу данных, когда пир регистрируется на Asterisk. Значения: yes или no, по умолчанию – yes.
rtautoclear Автоматически истекает срок действия пиров по тому же расписанию, как если бы они только что зарегистрировались. Это приводит к тому, что пир удаляется из памяти, когда период регистрации истек, пока этот узел не будет запрошен снова (например, через регистрацию или совершение вызова). Значения yes, no или целое значение, которое приводит к удалению пиров из памяти после этого числа секунд вместо интервала регистрации.
ignoreregexpire Если этот параметр включен, пиры не удаляются из памяти по истечении периода регистрации. Вместо этого информация остается в памяти, так что при запросе вызова конечной точки с истекшей регистрацией будет использоваться последняя известная информация (IP-адрес, порт и т.д.).

После включения rtcachefriends=yes в sip.conf и перезагрузки chan_sip.so (с помощью module reload chan_sip.so) вы можете зарегистрировать своего пира Asterisk, используя Realtime, и пир должен быть занесен в память. Вы сможете проверить это, выполнив команду sip show peers в консоли Asterisk:

Name/username Host Dyn Port Status Realtime

0000FFFF0008/0000FFFF0008 172.16.0.160 D 5060 Unmonitored Cached RT

Если бы вы посмотрели на таблицу непосредственно в базе данных, вы бы увидили что-то вроде этого:

+——–+————–+———+———–+———+————–+

| type | name | secret | context | host | ipaddr |

+——–+————–+———+———–+———+————–+

| friend | 0000FFFF0008 | welcome | LocalSets | dynamic | 172.16.0.160 |

+——–+————–+———+———–+———+————–+

+——+————+————–+————————————-+

| port | regseconds | defaultuser | fullcontact |

+——+————+————–+————————————-+

| 5060 | 1283928895 | 0000FFFF0008 | sip:0000FFFF0008@172.16.0.160:52722 |

+——+————+————–+————————————-+

+———–+—————–+——–+

| regserver | useragent | lastms |

+———–+—————–+——–+

| NULL | Zoiper rev.6739 | 0 |

+———–+—————–+——–+

Есть еще много вариантов, которые мы можем определить для SIP friends, такие как CallerID; добавление этой информации так же просто, как добавление столбца callerid в таблицу. См. файл sip.conf.sample для получения дополнительных параметров, которые могут быть определены для SIP friends.

Хранение записей деталей вызовов (CDR)

Записи деталей вызовов (CDR) содержат информацию о вызовах, прошедших через вашу систему Asterisk. Они рассматриваются далее в Главе 24. Хранение CDR – популярное использование баз данных в Asterisk, поскольку упрощает управление ими (например, вы можете отслеживать несколько систем Asterisk в одной таблице). Кроме того, размещая записи в базе данных, вы открываете множество возможностей, включая создание собственного веб-интерфейса для отслеживания статистики, например, использование вызовов и наиболее часто вызываемых местоположений, биллинга или проверки счетов в телефонной компании.

Мы склонны предполагать, что поскольку хранение CDR в базе данных настолько полезно, вы всегда должны внедрять хранение CDR в базу данных на любой производственной системе (вы всегда можете хранить CDR в файле, так что ничего не потеряете).

Установка имени системы для глобальных уникальных идентификаторов

CDR состоит из уникального идентификатора и нескольких полей информации о вызове (включая исходный и целевой канал, продолжительность вызова, последнее приложение и т.д.). В кластерном наборе Asterisk теоретически возможно дублирование между уникальными идентификаторами, поскольку каждая система Asterisk рассматривает только себя. Чтобы решить эту проблему, мы можем автоматически добавлять системный идентификатор в начало уникальных идентификаторов, добавив параметр /etc/asterisk/asterisk.conf. Для каждого из ваших Asterisk задайте идентификатор, добавив что-то вроде:

[options]

systemname = toronto

Лучший способ сохранить ваши данные о деталях звонков – через модуль cdr_adaptive_odbc. Этот модуль позволяет вам выбрать какие столбцы данных, встроенные в Asterisk, будут храниться в вашей таблице и это позволит вам добавить дополнительные столбцы, которые могут быть заполнены функцией диалплана CDR(). Вы можете даже хранить разные части данных CDR в разных таблицах и базах данных, если это необходимо.19

Более подробная информация о стандартных столбцах CDR в Asterisk приведена в Таблице 24-2. На момент написания этой статьи здесь приведен пример команды SQL для создания значений столбцов по умолчанию:

CREATE TABLE cdr (

calldate datetime NOT NULL default ‘0000-00-00 00:00:00’,

clid varchar(80) NOT NULL default ”,

src varchar(80) NOT NULL default ”,

dst varchar(80 ) NOT NULL default ”,

dcontext varchar(80) NOT NULL default ”,

channel varchar(80) NOT NULL default ”,

dstchannel varchar(80) NOT NULL default ”,

lastapp varchar(80) NOT NULL default ” ,

lastdata varchar(80) NOT NULL default ”,

duration int(11) NOT NULL default ‘0’,

billsec int(11) NOT NULL default ‘0’,

disposition varchar(45) NOT NULL default ”,

amaflags int( 11) NOT NULL default ‘0’,

accountcode varchar(20) NOT NULL default ”,

uniqueid varchar(32) NOT NULL default ”,

userfield varchar(255) NOT NULL default ”,

peeraccount varchar(20) NOT NULL default ”,

linkedid varchar(32) NOT NULL default ”,

sequence int(11) NOT NULL default ‘0’

);

Вы можете определить все или любую часть этих записей в базе данных, и Asterisk будет работать над тем, что доступно. Вы также можете добавить больше столбцов для хранения других данных, относящихся к вызовам. Например, если вы хотите внедрить маршрутизацию с наименьшей стоимостью (least-cost routing – LCR), вы можете добавить столбцы для маршрута, стоимость минуты и поминутную тарификацию. После того, как вы добавили эти столбцы, они могут быть заполнены с помощью диалплана с помощью функции CDR() (например, Set(CDR(per_minute_rate)=0.01)20).

После создания таблицы с именем cdr в базе данных вы должны настроить cdr_adap tive_odbc.conf в каталоге /etc/asterisk. В следующем примере используется соединение asterisk, которое мы определили в res_odbc.conf и сохраним данные в таблице cdr:

; cdr_adaptive_odbc.conf

[adaptive_connection]

connection=asterisk

table=cdr

Да, действительно, это все, что вам нужно. После настройки cdr_adaptive_odbc.conf просто перезагрузите модуль cdr_adaptive_odbc.so из консоли Asterisk, запустив module reload cdr_adaptive_odbc.so. Вы можете проверить, загружен ли адаптер Adaptive ODBC путем запуска cdr show status:21

*CLI> cdr show status

Call Detail Record (CDR) settings

———————————-

Logging: Enabled

Mode: Simple

Log unanswered calls: No

Log congestion: No

* Registered Backends

——————-

cdr-syslog

Adaptive ODBC

cdr-custom

csv

cdr_manager

Теперь совершите вызов, на который будет дан ответ (например, с помощью функции Playback() или Dial() на другой канал с ответом на него). Вы должны получить некоторые CDR, хранящиеся в вашей базе данных. Вы можете проверить, запустив SELECT * FROM CDR; из консоли базы данных.

С основной информацией CDR, хранящейся в базе данных, вы можете добавить дополнительную информацию в таблицу cdr, такую как рейтинг маршрута (route rate). Вы можете использовать директиву ALTER TABLE, чтобы добавить столбец с именем route_rate в таблицу:

sql> ALTER TABLE cdr ADD COLUMN route_rate varchar(10);

Теперь перезагрузите модуль cdr_adaptive_odbc.so из консоли Asterisk:

*CLI> module reload cdr_adaptive_odbc.so

и заполните новый столбец из диалплана Asterisk, используя функцию CDR(), например:

exten => _NXXNXXXXXX,1,Verbose(1,Example of adaptive ODBC usage)

same => n,Set(CDR(route_rate)=0.01)

same => n,Dial(SIP/my_itsp/${EXTEN})

same => n,Hangup()

После изменения в вашей базе данных и диалплане, вы можете позвонить, а затем посмотреть на свои CDR. Вы должны увидеть что-то вроде следующего:

+————–+———-+———+————+

| src | duration | billsec | route_rate |

+————–+———-+———+————+

| 0000FFFF0008 | 37 | 30 | 0.01 |

+————–+———-+———+————+

На самом деле сохранение рейтинга в записи вызовов может быть неидеальным (CDR обычно используется в качестве исходного ресурса, и такие вещи как рейтинг, добавляются ниже с помощью программного обеспечения для биллинга). Возможность добавления настраиваемых полей в CDR очень полезна, но будьте осторожны чтобы не использовать ваши записи вызовов для замены правильной биллинговой платформы. Лучше всего сохранить свой CDR в чистоте и делать дальнейшую обработку ниже.

Дополнительные параметры конфигурации для cdr_adaptive_odbc.conf

Некоторые дополнительные параметры конфигурации существуют в файле cdr_adaptive_odbc.conf, который может быть нам полезен. Во-первых, вы можете определить несколько баз данных или таблиц для хранения информации, поэтому если у вас есть несколько баз данных, которые нуждаются в одной и той же информации, вы можете просто определить их в res_odbc.conf, создать таблицы в базах данных, а затем обратиться к ним в отдельных разделах конфигурации:

[mysql_connection]

connection=asterisk_mysql

table=cdr

[mssql_connection]

connection=production_mssql

table=call_records

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

Помимо простой настройки нескольких соединений и таблиц (которые, конечно же, могут содержать или не содержать одну и ту же информацию, модуль CDR, который мы используем, адаптивен к подобным ситуациям), мы можем определить псевдонимы для встроенных переменных, таких как accountcode, src, dst, billsec и т.д.

Если бы мы добавили псевдонимы для имен столбцов для нашего MS SQL-соединения, мы могли бы изменить наше определение соединения следующим образом:

[mssql_connection]

connection=production_mssql

table=call_records

alias src => Source

alias dst => Destination

alias accountcode => AccountCode

alias billsec => BillableTime

В некоторых ситуациях вы можете указать соединение, в котором хотите только регистрировать вызовы из определенного источника или для определенного адресата. Мы можем сделать это с помощью фильтров:

[logging_for_device_0000FFFF0008]

connection=asterisk_mysql

table=cdr_for_0000FFFF0008

filter src => 0000FFFF0008

Если вам нужно заполнить определенный столбец информацией на основе имени раздела, его можно задать статически с помощью параметра static, который можно использовать с параметром filter:

[mysql_connection]

connection=asterisk_mysql

table=cdr

[filter_mysql_connection]

connection=asterisk_mysql

table=cdr

filter src => 0000FFFF0008

static «DoNotCharge» => accountcode

В предыдущем примере вы получите дубликаты записей в той же таблице, но вся информация будет одинаковой, за исключением заполненного столбца accountcode, поэтому вы сможете отфильтровать её с помощью SQL.

Хранение сообщений голосовой почты ODBC

Asterisk позволяет хранить голосовую почту внутри базы данных с помощью ODBC-коннектора. Это полезно в кластерной среде, где вы хотите абстрагировать данные голосовой почты из локальной системы, чтобы несколько блоков Asterisk имели доступ к одним и тем же данным. Конечно, вы должны принять во внимание что вы централизуете часть Asterisk и необходимо защитить эти данные от потерь, например, делать регулярные резервные копии и, возможно, кластеризовать базу данных с помощью репликации.

Asterisk хранит каждое сообщение голосовой почты внутри Binary Large OBject (BLOB). При извлечении данных он извлекает информацию из BLOB и временно сохраняет ее на жестком диске, пока она воспроизводится пользователю. Затем Asterisk удаляет BLOB и запись из базы данных, когда пользователь удаляет голосовую почту. Многие базы данных, такие как MySQL, содержат встроенную поддержку BLOB, но, как вы увидите, с PostgreSQL требуется несколько дополнительных шагов для использования этой функции. После завершения этого раздела вы сможете записывать, воспроизводить и удалять данные голосовой почты из базы данных так же, как если бы они хранились на локальном жестком диске.

Этот раздел основывается на предыдущих разделах конфигурации в этой главе. Если вы еще этого не сделали, обязательно выполните действия, описанные в разделах «Установка PostgreSQL для RHEL» и «Установка и настройка ODBC» прежде чем продолжать. В последнем убедитесь, что вы включили ODBC_STORAGE в системе menuselect в разделе Voicemail Options.

Альтернативный метод централизации

Сохранение голосовой почты в базе данных – один из способов централизовать голосовую почту. Другой метод – запустить автономный сервер голосовой почты, как мы обсуждали в Главе 5.

В этом разделе мы обсудим хранение голосовых сообщений в базе данных. Это не имеет никакого отношения к хранению фактических конфигураций почтовых ящиков (т.е. каждого почтового ящика). Если вы хотите это сделать, вам нужно будет создать таблицу для обработки записей voicemail.conf с использованием статического или динамического Realtime (обсуждалось ранее в этой главе).

Компиляция модуля app_voicemail для поддержки хранилища ODBC

Чтобы поддерживать запись голосовых сообщений в базу данных ODBC, эта возможность должна быть скомпилирована в модуле голосовой почты.

Чтобы все это работало, необходимо уже скомпилировать поддержку ODBC в Asterisk. Дополнительные сведения см. в разделе «Установка и настройка ODBC».

Перейдите в каталог, в который вы загрузили исходный код Asterisk. Если вы следовали Главе 3, вы должны найти его где-то здесь:

cd ~/src/asterisk-complete/asterisk/11

Запустить make с аргументом menuselect:

$ sudo make menuselect

Это запустит интерфейс Asterisk Module and Build Selection. Вам нужно будет перейти к Voicemail Build Options и выбрать ODBC_STORAGE:

Asterisk Module and Build Option Selection

Voicemail Build Options

— core —

( ) FILE_STORAGE

[*] ODBC_STORAGE

XXX IMAP_STORAGE

Затем просто сохраните и запустите:

$ make install

и перезапустите Asterisk. Ваша голосовая почта готова к записи в базу данных!

Создание типа Large Object для PostgreSQL

Хотя MySQL имеет тип BLOB (Binary Large OBject), мы должны сообщить PostgreSQL, как обращаться с большими (large) объектами.22 Это включает создание триггера для очистки данных, когда мы удаляем из базы данных запись, которая ссылается на large-объект.

Подключитесь к базе данных как пользователь asterisk из консоли:

$ psql -h localhost -U asterisk asterisk

Password:

Вы должны быть superuser для выполнения следующего кода. Кроме того, если вы используете пользователя postgres для создания таблицы – вам нужно будет использовать SQL-директиву ALTER TABLE, чтобы изменить владельца на пользователя asterisk.

В консоли PostgreSQL запустите следующий скрипт для создания объекта типа large:

CREATE FUNCTION loin (cstring) RETURNS lo AS ‘oidin’ LANGUAGE internal

IMMUTABLE STRICT;

CREATE FUNCTION loout (lo) RETURNS cstring AS ‘oidout’ LANGUAGE internal

IMMUTABLE STRICT;

CREATE FUNCTION lorecv (internal) RETURNS lo AS ‘oidrecv’ LANGUAGE internal

IMMUTABLE STRICT;

CREATE FUNCTION losend (lo) RETURNS bytea AS ‘oidrecv’ LANGUAGE internal

IMMUTABLE STRICT;

CREATE TYPE lo ( INPUT = loin, OUTPUT = loout, RECEIVE = lorecv, SEND = losend,

INTERNALLENGTH = 4, PASSEDBYVALUE );

CREATE CAST (lo AS oid) WITHOUT FUNCTION AS IMPLICIT;

CREATE CAST (oid AS lo) WITHOUT FUNCTION AS IMPLICIT;

Мы будем использовать процедурный язык PostgreSQL, называемый pgSQL/PL, для создания функции. Эта функция будет вызываться из триггера, который запускается всякий раз, когда мы изменяем или удаляем запись в таблице, используемой для хранения сообщений голосовой почты. Это значит, что данные очищаются и не остаются сиротой в базе данных:

CREATE FUNCTION vm_lo_cleanup() RETURNS “trigger”

AS $$

declare

msgcount INTEGER;

begin

— raise notice ‘Starting lo_cleanup function for large object with oid

%’,old.recording;

— If it is an update action but the BLOB (lo) field was not changed,

don’t do anything

if (TG_OP = ‘UPDATE’) then

if ((old.recording = new.recording) or (old.recording is NULL)) then

raise notice ‘Not cleaning up the large object table,

as recording has not changed’;

return new;

end if;

end if;

if (old.recording IS NOT NULL) then

SELECT INTO msgcount COUNT(*) AS COUNT FROM voicemessages WHERE recording

= old.recording;

if (msgcount > 0) then

raise notice ‘Not deleting record from the large object table, as object

is still referenced’;

return new;

else

perform lo_unlink(old.recording);

if found then

raise notice ‘Cleaning up the large object table’;

return new;

else

raise exception ‘Failed to clean up the large object table’;

return old;

end if;

end if;

else

raise notice ‘No need to clean up the large object table,

no recording on old row’;

return new;

end if;

end$$

LANGUAGE plpgsql;

Мы создадим таблицу voicemessages, в которой будет храниться информация голосовой почты:

CREATE TABLE voicemessages

(

uniqueid serial PRIMARY KEY,

msgnum int4,

dir varchar(80),

context varchar(80),

macrocontext varchar(80),

callerid varchar(40),

origtime varchar(40),

duration varchar(20),

mailboxuser varchar(80),

mailboxcontext varchar(80),

recording lo,

label varchar(30),

“read” bool DEFAULT false,

flag varchar(10)

);

И теперь нам нужно связать триггер с нашей вновь созданной таблицей, чтобы выполнять очистку при каждом изменении или удалении записи в таблице voicemessages:

CREATE TRIGGER vm_cleanup AFTER DELETE OR UPDATE ON voicemessages FOR EACH ROW

EXECUTE PROCEDURE vm_lo_cleanup();

Макет таблицы хранения голосовой почты ODBC

Мы будем использовать таблицу voicemessages для хранения информации голосовой почты в базе данных через ODBC-подключение. Таблица 16-6 описывает конфигурацию таблицы для хранения голосовой почты ODBC. Если вы используете базу данных PostgreSQL, определение таблицы и поддержка large-объектов были настроены в предыдущем разделе.

Таблица 16-6. Макет таблицы хранения голосовой почты ODBC

Имя столбца Тип столбца
uniqueid последовательный, первичный ключ
dir символьный (до 80)
msgnum целочисленный
recording BLOB (Binary Large OBject)
context символьный (до 80)
macrocontext символьный (до 80)
callerid символьный (до 40)
origtime символьный (до 40)
duration символьный (до 20)
mailboxuser символьный (до 80)
mailboxcontext символьный (до 80)
label символьный (до 30)
reada логический, по умолчанию false
flag символьный (до 10)

a read является зарезервированным словом как в MySQL, так и в PostgreSQL (и, вероятно, в других базах данных), что означает, что вам нужно будет этого избегать имени столбца при его создании. В MySQL это делается с помощью обратных кавычек (`) вокруг слова read при создании таблицы, а в PostgreSQL с двойными кавычками (“). В MS SQL вы будете использовать квадратные скобки, например, [read].

… вы абсолютно уверены, что вам нужно хранить ваши сообщения в таблице базы данных?

Вот пример того, как создать эту таблицу в MySQL:

CREATE TABLE voicemessages

(

uniqueid serial PRIMARY KEY,

msgnum int(4),

dir varchar(80),

context varchar(80),

macrocontext varchar(80),

callerid varchar(40),

origtime varchar(40),

duration varchar(20),

mailboxuser varchar(80),

mailboxcontext varchar(80),

recording blob,

label varchar(30),

`read` bool DEFAULT false,

flag varchar(10)

);

пример для PostgreSQL приведен в предыдущем разделе.

Настройка voicemail.conf для ODBC-хранилища

Особо нечего добавить к файлу voicemail.conf для включения хранилища голосовой почты ODBC. На самом деле, всего три строчки! У вас, вероятно, есть несколько типов форматов, определенных в разделе [general] voicemail.conf, но нам нужно установить один формат, потому что мы можем сохранить только один файл (формат) в базу данных. Формат WAV49 представляет собой сжатый формат файла WAV, который должен воспроизводиться на настольных компьютерах Linux и Microsoft Windows.

Опция odbcstorage ссылается на имя, указанное вами в res_odbc.conf (если вы следовали этой главе, то мы назвали его asterisk). Параметр odbctable ссылается на таблицу, в которой должна храниться информация голосовой почты. В примерах в этой главе мы используем таблицу с именем voicemessages.

Измените раздел [general] вашего voicemail.conf так, чтобы были установлены следующие значения:

[general]

format=wav49

odbcstorage=asterisk

odbctable=voicemessages

Для создания пользователей вы можете либо разделить контекст голосовой почты, либо просто использовать раздел голосовой почты default. Кроме того, вы можете пропустить создание нового пользователя и использовать существующего пользователя, например 0000FFFF0001. Мы определим почтовый ящик в разделе по умолчанию для voicemail.conf следующим образом:

[default]

1000 => 1000,J.P. Wiser

Вы также можете использовать голосовую почту, определенную в файле extconfig.conf для загрузки своих пользователей из базы данных. См. “Динамический Realtime” для получения дополнительной информации о настройке определенных параметров конфигурации модуля в базе данных и “Статический Realtime“для получения дополнительной информации о загрузке остальной части файла конфигурации.

Теперь подключитесь к консоли Asterisk и выгрузите, затем загрузите модуль app_voicemail.so:

*CLI> module unload app_voicemail.so

== Unregistered application ‘VoiceMail’

== Unregistered application ‘VoiceMailMain’

== Unregistered application ‘MailboxExists’

== Unregistered application ‘VMAuthenticate’

*CLI> module load app_voicemail.so

Loaded /usr/lib/asterisk/modules/app_voicemail.so =>

(Comedian Mail (Voicemail System))

== Registered application ‘VoiceMail’

== Registered application ‘VoiceMailMain’

== Registered application ‘MailboxExists’

== Registered application ‘VMAuthenticate’

== Parsing ‘/etc/asterisk/voicemail.conf’: Found

Затем убедитесь, что ваш новый почтовый ящик загружен успешно:

*CLI> voicemail show users for default

Context Mbox User Zone NewMsg

default 1000 J.P. Wiser 0

Тестирование хранилища голосовых сообщений ODBC

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

[odbc_vm_test]

exten => 100,1,VoiceMail(1000@default) ; оставить сообщение

exten => 200,1,VoiceMailMain(1000@default) ; получить сообщение

В приведенном выше примере диалплан будет доступен только устройствам, которым доступен контекст [odbc_vm_test] или любой другой контекст в диалплане, который имеет оператор include => odbc_vm_test для разрешения вызова этого контекста из другого.

После обновления файла extensions.conf обязательно перезагрузите диалплан:

* CLI> dialplan reload

Затем настройте свой телефон или клиент с именем пользователя odbc_test_user и паролем <supersecret> совершите вызов на номер 100 и оставьте голосовую почту. В случае успеха вы должны увидеть нечто вроде:

— Executing VoiceMail(“SIP/odbc_test_user-10228cac”, “1000@default”) in new

stack

— Playing ‘vm-intro’ (language ‘en’)

— Playing ‘beep’ (language ‘en’)

— Recording the message

— x=0, open writing: /var/spool/asterisk/voicemail/default/1000/tmp/dlZunm

format: wav49, 0x101f6534

— User ended message by pressing #

— Playing ‘auth-thankyou’ (language ‘en’)

== Parsing ‘/var/spool/asterisk/voicemail/default/1000/INBOX/msg0000.txt’: Found

На этом этапе вы можете проверить базу данных, чтобы убедиться, что ваши данные были успешно записаны. Дополнительную информацию см. в следующих разделах.

Теперь, когда вы удостоверились, что все правильно сохранилось в базе данных, то можете попробовать прослушать почту через приложение VoiceMailMain(), набрав 200:

*CLI>

— Executing VoiceMailMain(“SIP/odbc_test_user-10228cac”,

“1000@default”) in new stack

— Playing ‘vm-password’ (language ‘en’)

— Playing ‘vm-youhave’ (language ‘en’)

— Playing ‘digits/1’ (language ‘en’)

— Playing ‘vm-INBOX’ (language ‘en’)

— Playing ‘vm-message’ (language ‘en’)

— Playing ‘vm-onefor’ (language ‘en’)

— Playing ‘vm-INBOX’ (language ‘en’)

— Playing ‘vm-messages’ (language ‘en’)

— Playing ‘vm-opts’ (language ‘en’)

— Playing ‘vm-first’ (language ‘en’)

— Playing ‘vm-message’ (language ‘en’)

== Parsing ‘/var/spool/asterisk/voicemail/default/1000/INBOX/msg0000.txt’: Found

Проверка двоичных данных, хранящихся в PostgreSQL

Чтобы убедиться что запись действительно сделана в базу данных, используйте приложение psql:

$ psql -h localhost -U asterisk asterisk

Password:

Далее запустить оператор SELECT чтобы убедиться, что у вас есть данные в таблице voicemessages:

localhost=# SELECT uniqueid,dir,callerid,mailboxcontext,

recording FROM voicemessages;

uniqueid | dir | callerid

———+————————————————–+————–

1 | /var/spool/asterisk/voicemail/default/1000/INBOX | +18005551212

| mailboxcontext | recording |

+—————-+———–+

| default | 47395 |

(1 row)

Если запись была помещена в базу данных, вы должны получить строку возврата. Вы заметите, что столбец recording содержит число (которое, безусловно, будет отличаться от того, что указано здесь), которое на самом деле является идентификатором large-объекта, хранящегося в системной таблице. Вы можете убедиться в том что large-объект существует в этой системной таблице с помощью команды lo_list:

localhost=# \lo_list

Large objects

ID | Description

——-+————-

47395 |

(1 row)

Как вы убедились – идентификатор объекта в таблице voicemessages соответствует тому, что указано в large-объекте системной таблицы. Можно также извлечь данные из базы данных и сохранить их на жестком диске:

localhost=# \lo_export 47395 /tmp/voicemail-47395.wav

lo_export

Затем проверьте аудио в вашем любимом звуковом приложении, таком как play:

$ play /tmp/voicemail-47395.wav

Input Filename : /tmp/voicemail-47395.wav

Sample Size : 8-bits

Sample Encoding: wav

Channels : 1

Sample Rate : 8000

Time: 00:06.22 [00:00.00] of 00:00.00 ( 0.0%) Output Buffer: 298.36K

Done.

Проверка двоичных данных, хранящихся в MySQL

Чтобы убедиться, что ваши данные записаны правильно, вы можете использовать приложение mysql для входа в вашу базу данных и экспорта записи голосовой почты в файл:

$ mysql -u asterisk -p asterisk

Enter password:

после входа в систему в базе данных вы можете использовать оператор SELECT для сброса содержимого записи в файл. Во-первых, убедитесь, что в вашей таблице voicemessages есть хотя бы одна запись:23

mysql> SELECT uniqueid, msgnum, callerid, mailboxuser, mailboxcontext, `read`

-> FROM voicemessages;

+———-+——–+——————————+————-

| uniqueid | msgnum | callerid | mailboxuser

+———-+——–+——————————+————-

| 1 | 0 | “Leif Madsen” <100> | 100

| 2 | 1 | “Leif Madsen” <100> | 100

| 3 | 2 | “Leif Madsen” <100> | 100

| 5 | 0 | “Julie Bryant” <12565551111> | 100

+———-+——–+——————————+————-

+—————-+——+

| mailboxcontext | read |

+—————-+——+

| shifteight.org | 0 |

| shifteight.org | 0 |

| shifteight.org | 0 |

| default | 0 |

+—————-+——+

Вы также можете добавить столбец recording в оператор SELECT, но в итоге получите много тарабарщины на экране.

После проверки наличия данных в таблице voicemessages можно экспортировать одну из записей и воспроизвести ее из консоли:

mysql> SELECT recording FROM voicemessages WHERE uniqueid = ‘5’

-> DUMPFILE ‘/tmp/voicemail_recording.wav’;

Пользователю, под которым вы экспортируете данные, должен иметь разрешение FILE в MySQL, что означает, что ему должен быть предоставлен доступ ALL. Если вы не предоставили пользователю asterisk права доступа ALL, для экспорта файла необходимо использовать пользователя root.

Теперь выйдите из консоли MySQL и используйте приложениес play из консоли (при условии, что у вас есть динамики и звуковая карта, настроенная в вашей системе Asterisk, которую вы можете использовать если собираетесь использовать ее для служебного пейджинга) или скопировать файл в другую ситсему и слушать его там:

$ play /tmp/voicemail_recording.wav

voicemail_recording.wav:

File Size: 7.28k Bit Rate: 13.1k

Encoding: GSM

Channels: 1 @ 16-bit

Samplerate: 8000Hz

Replaygain: off

Duration: 00:00:04.44

In:100% 00:00:04.44 [00:00:00.00] Out:35.5k [ | ] Hd:4.4 Clip:0

Done.

Интеграция базы данных очередей ACD

Одним из преимуществ хранения конфигурации в базе данных является упрощение создания интерфейсов для управления данными (теоретически это можно сделать и с конфигурационными файлами, однако большинство веб-фреймворков или сред разработки интерфейсов предполагают, что данные конфигурации будут храниться в базе данных).

В колл-центре (часто называемым очередями ACD) может быть очень полезно иметь возможность настроить параметры очереди без необходимости редактировать и перезагружать конфигурационные файлы. Управление колл-центром может быть сложной задачей, а упрощение настройки параметров может сделать жизнь каждого человека намного проще.

Хранение queues.conf в базе данных

Сохранение queues.conf в базе данных работает так же, как и с любым другим файлом конфигурации. У вас есть выбор – использование статического или динамического Realtime.

Хранение queues.conf с использованием статического Realtime

Для статического Realtime24 параметры хранятся точно так же, как для любого другого файла конфигурации, как описано в разделе “Статический Realtime“.

Во-первых, в extconfig.conf вам нужен раздел, который указывает, что queues.conf будет храниться в базе данных:

vim /etc/asterisk/extconfig.conf

[settings]

queues.conf => odbc,asterisk,ast_config

Во-вторых, в самой таблице вам нужно сохранить параметры очереди, как они появляются в файле queues.conf.

Пример синтаксиса для однострочной записи будет выглядеть примерно так:

mysql> insert into ast_config

(cat_metric,var_metric,filename,category,var_name,var_val,commented)

VALUES

(2,1,’queues.conf’,’firstqueue’,’strategy’,’rrmemory’,0);

Это приведет к записи в таблице, выглядящей как Таблица 16-7.

Таблица 16-7. Пример записи таблицы

id cat_metric var_metric filename category var_name var_val commented
3 2 1 queues.conf first queue strategy rrmemory 0

Вам потребуется несколько десятков записей чтобы правильно воспроизвести файл queues.conf.

Мы рекомендуем использовать динамический Realtime для хранения ваших параметров очереди.

Хранение queues.conf с использованием динамического Realtime

Хранение параметров очереди в таблице имеет гораздо больший смысл, если вы используете динамический Realtime. Созданная вами таблица будет немного легче для глаз (хотя она может содержать много столбцов25) и каждая очередь будет определена в одной записи:

CREATE TABLE `Queues` (

`QueueID` mediumint(8) unsigned NOT NULL auto_increment,

`name` varchar(128) NOT NULL COMMENT ‘Asterisk’s name for the queue’,

`description` varchar(128) default NULL,

`maxlen` tinyint(4) default NULL,

`reportholdtime` varchar(3) default ‘no’,

`periodic_announce_frequency` varchar(4) default NULL,

`periodic_announce` varchar(128) default NULL,

`strategy` varchar(20) NOT NULL default ‘rrmemory’,

`joinempty` varchar(35) default ‘no’,

`leavewhenempty` varchar(35) default ‘no’,

`autopause` varchar(3) default ‘no’,

`announce_round_seconds` varchar(4) default NULL,

`retry` varchar(4) default NULL,

`wrapuptime` varchar(4) default NULL,

`announce_holdtime` varchar(3) default ‘no’,

`announce_frequency` varchar(4) default ‘0’,

`timeout` varchar(4) default ’60’,

`context` varchar(128) NOT NULL,

`musicclass` varchar(128) default ‘default’,

`autofill` varchar(3) default ‘yes’,

`ringinuse` varchar(45) default ‘no’,

`musiconhold` varchar(128) default ‘yes’,

`monitor_type` varchar(128) default ‘MixMonitor’,

`monitor_format` varchar(128) default ‘wav’,

`servicelevel` varchar(4) default ’60’,

`queue_thankyou` varchar(128) default ”,

`queue_youarenext` varchar(128) default ”,

`queue_thereare` varchar(128) default ”,

`queue_callswaiting` varchar(128) default ”,

`queue_holdtime` varchar(128) default ”,

`queue_minutes` varchar(128) default ”,

`queue_seconds` varchar(128) default ”,

`queue_lessthan` varchar(128) default ”,

`queue_reporthold` varchar(128) default ”,

`relative_periodic_announce` varchar(4) default ‘yes’,

PRIMARY KEY (`QueueID`),

UNIQUE KEY `name_UNIQUE` (`name`),

UNIQUE KEY `UniqueID_UNIQUE` (`QueueID`)

)

Затем вы просто редактируете файл extconfig.conf чтобы сообщить Asterisk о своем намерении использовать эту таблицу для хранения конфигураций очередей:26

vim /etc/asterisk/extconfig.conf

[settings]

queues => odbc,asterisk,queue_table

queue_members => odbc,asterisk,queue_member_table

В некоторых версиях Asterisk существует ошибка из-за которой очередь не загружается если нет ссылки на queue_members (даже если вы не планируете жестко привязывать своих участников очереди в таблице).

CREATE TABLE queue_member_table (

uniqueid INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT,

membername varchar(40),

queue_name varchar(128),

interface varchar(128),

penalty INT(11),

paused INT(11),

UNIQUE KEY queue_interface (queue_name, interface)

);

queue_member_table не должна иметь никаких данных, хранящихся в ней. Она просто должна существовать.

После перезапуска Asterisk, ваши очереди теперь должны быть доступны для просмотра через команду queue show и, конечно, вы также сможете использовать их в вашем диалплане.

Хранение параметров диалплана для очереди в базе данных

Приложение диалплана Queue() позволяет передавать несколько параметров. Команда CLI core show application Queue определяет следующий синтаксис:

[Syntax]

Queue(queuename[,options[,URL[,announceoverride[,timeout[,AGI[,macro[,gosub[,

rule[,position]]]]]]]]])

Поскольку мы сохраняем нашу очередь в базе данных, почему бы не сохранить параметры, которые вы хотите передать в очередь, аналогичным образом?

CREATE TABLE `QueueDialplanParameters` (

`QueueDialplanParametersID` mediumint(8) NOT NULL auto_increment,

`Description` varchar(128) NOT NULL,

`QueueID` mediumint(8) unsigned NOT NULL COMMENT ‘Pointer to queues_table’,

`options` varchar(45) default ‘n’,

`URL` varchar(256) default NULL,

`announceoverride` bit(1) default NULL,

`timeout` varchar(8) default NULL,

`AGI` varchar(128) default NULL,

`macro` varchar(128) default NULL,

`gosub` varchar(128) default NULL,

`rule` varchar(128) default NULL,

`position` tinyint(4) default NULL,

`queue_tableName` varchar(128) NOT NULL,

PRIMARY KEY (`QueueDialplanParametersID`)

);

Используя func_odbc вы можете написать функцию, которая вернет параметры диалплана, относящиеся к этой очереди:

[QUEUE_DETAILS]

prefix=GET

dsn=asterisk

readsql=SELECT * FROM QueueDialplanParameters

readsql+= WHERE QueueDialplanParametersID=’${ARG1}’

Затем передайте эти параметры в приложение Queue() по мере поступления вызовов:

exten => s,1,Verbose(1,Call entering queue named ${SomeValidID)

same => n,Set(QueueParameters=${GET_QUEUE_DETAILS(SomeValidID)})

same => n,Queue(${QueueParameters})

В то время как несколько сложнее разработать, чем просто написать соответствующий диалплан, преимущество состоит в том, что вы сможете управлять большим количеством очередей с более широким спектром параметров используя диалплан, который достаточно гибок, чтобы обрабатывать любые параметры, принимаемые приложением очередей в Asterisk. Мы думаем что для чего-то большего, чем простая очередь, вы найдете что использование базы данных для всего этого будет стоить усилий.

Запись queue_log в базу данных.

Наконец, мы можем хранить наш журнал queue_log в базе данных, что упростит внешним приложениям извлечение данных о производительности очереди из системы:

CREATE TABLE queue_log (

id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,

time char(26) default NULL,

callid varchar(32) NOT NULL default ”,

queuename varchar(32) NOT NULL default ”,

agent varchar(32) NOT NULL default ”,

event varchar(32) NOT NULL default ”,

data1 varchar(100) NOT NULL default ”,

data2 varchar(100) NOT NULL default ”,

data3 varchar(100) NOT NULL default ”,

data4 varchar(100) NOT NULL default ”,

data5 varchar(100) NOT NULL default ”,

PRIMARY KEY (`id`)

);

Отредактируйте файл extconfig.conf, чтобы обратиться к таблице queue_log:

[settings]

queue_log => odbc, asterisk, queue_log

Перезапускаем Asterisk и ваша очередь теперь будут записывать информацию в базу данных. Например, вход агента в очередь продаж должно приводить к следующему:

mysql> select * from queue_log;

+—-+—————————-+———————-+———–+

| id | time | callid | queuename |

+—-+—————————-+———————-+———–+

| 1 | 2013-01-22 15:07:49.772263 | NONE | NONE |

| 2 | 2013-01-22 15:07:49.809028 | toronto-1358885269.1 | support |

+—-+—————————-+———————-+———–+

+——————+————+——-+——-+——-+——-+——-+

| agent | event | data1 | data2 | data3 | data4 | data5 |

+——————+————+——-+——-+——-+——-+——-+

| NONE | QUEUESTART | | | | | |

| SIP/0000FFFF0001 | ADDMEMBER | | | | | |

+——————+————+——-+——-+——-+——-+——-+

Если вы разрабатываете какое-либо внешнее приложение, нуждающееся в доступе к статистике очередей, то хранение данных будет намного превосходить использование /var/log/asterisk/queue_log.

Заключение

В этой главе мы узнали о нескольких областях, в которых Asterisk может интегрироваться с реляционной базой данных. Это полезно для систем где необходимо масштабирование путем кластеризации нескольких блоков Asterisk, работающих с одной и той же централизованной информацией или когда вы хотите начать создавать внешние приложения для изменения информации, не требующей перезагрузки системы (т.е. не требуя изменения файлов конфигурации).

1В большой, загруженной системе лучше установить базу данных на совершенно отдельный блок от вашей системы Asterisk.

2Если вы установили Ubuntu, значит вы уже установили пароль root. Вам нужно будет ввести этот пароль во время выполнения скрипта, после чего он скажет, что вы уже установили пароль root, поэтому вам не нужно его менять.

3Да, это слишком многословно. Единственные записи, которые вам действительно нужны, – это Driver, Database и Servername

4На самом деле это была проблема, с которой столкнулся один из авторов во время работы над этой книгой, и столбец флага был найден путем просмотра журнала операторов во время тестирования PostgreSQL.

5Лучшая из лучших.

6И если вы не знаете, что такое Dagwood, для этого есть Википедия. Я не настолько стар.

7Мы используем функцию SQL IF(), чтобы убедиться, что мы возвращаем значение 0 или 1. Это работает на MySQL 5.1 или выше. Если она не работает в вашей установке SQL, вы также можете проверить возвращаемый результат в диалплане, используя функцию IF().

8Обратите внимание, что в первом примере пользователю присваивается статус 1 и местоположение, в то время как во втором примере пользователю не задается значение для этих полей.

9Вы должны перезагрузить диалплан (dialplan reload) и func_odbc (module reload func_odbc.so) и протестировать диалплан до сих пор (наберите 991101 с одного из аппаратов, которые вы назначили этому контексту). Убедитесь, что уровень детализации консоли равен по крайней мере 3 (core set verbose 3), так вы сможете увидеть как этот диалплан работает в консоли (вызов этого диалплана будет возвращать быструю занятость, даже если он работает успешно).

10Это также может создать ненужную угрозу безопасности.

11Дополнительные сведения и примеры анализа нескольких строк, возвращаемых из базы данных, см. также в разделе “Мультистроковая функциональность с func_odbc”.

12Конфигурация реального времени, далее везде используется Realtime.

13Да, вызов этого “реального времени” несколько вводит в заблуждение, так как обновления данных не повлияют ни на что происходящее в реальном времени (пока не будет выполнена перезагрузка соответствующего модуля).

14pgcluster кажется мертвым проектом, а Postgres-R, похоже, находится в зачаточном состоянии, поэтому в настоящее время не может быть хорошего решения для репликации master-master с использованием PostgreSQL.

15Есть несколько учебников в Интернете, описывающих, как настроить репликацию с MySQL.

16Файл musiconhold.conf также может быть загружен через dynamic realtime (и это может быть лучшим выбором для производственной системы), но мы используем его статически, поскольку это простой файл, который является хорошим примером.

17Предупреждение: таблица Realtime ast_sippeers@asterisk требует столбец “*”, но этот столбец не существует!

18не-используйте-этот-пароль-выберите-другой

19Мы не уверены, что это хорошая идея, но приятно знать, что это возможно.

20Которая не будет работать, если вы не создали в таблице CDR поле per_minute_rate.

21Вы можете увидеть различные зарегистрированные бэкенды, в зависимости от того, какую конфигурацию вы сделали с другими компонентами различных модулей CDR.

22Если вы не можете хранить голосовые сообщения как BLOB-данные, но вы являетесь пользователем Postgres, можем ли мы предложить вам создать выделенный сервер баз данных для хранения голосовых сообщений и установить MySQL именно для этой цели? Здесь много сложностей, и мы не можем обещать, что это не будет стоить усилий.

23Если вы привыкли использовать SELECT * FROM при перечислении табличных данных, имейте в виду, что данные, хранящиеся в BLOB, могут не отображать ничего или, по крайней мере, ничего, что можно было бы прочитать. Запрос, предназначенный для создания текста в качестве выходных данных, не сможет обрабатывать двоичные данные должным образом.

24Рекомендуется использовать динамический Realtime, как описано в следующем разделе.

25Если столбец не определен, в очередь будет загружено значение по умолчанию для этого столбца. Если app_queue не распознает столбец, он будет проигнорирован.

26В Realtime вы не можете выбрать какое имя будет использоваться для ссылки на модуль. Например, хранение queues.conf в базе данных Realtime требует чтобы вы ссылались на него по имени queues =>.

Остались вопросы?

Я - Кондрашин Игорь, менеджер компании Voxlink. Хотите уточнить детали или готовы оставить заявку? Укажите номер телефона, я перезвоню в течение 3-х секунд.

VoIP оборудование

ближайшие курсы

ближайшие Вебинары

ONLINE

Why Choose HUGE?

Unlimited pre-designed elements

Each and every design element is designed for retina ready display on all kind of devices

User friendly interface and design

Each and every design element is designed for retina ready display on all kind of devices

100% editable layered PSD files

Each and every design element is designed for retina ready display on all kind of devices

Created using shape layers

Each and every design element is designed for retina ready display on all kind of devices