Глава 12
Введение
В этой главе мы рассмотрим несколько возможностей и функций Астериска совместно с базой данных (далее БД). В операционной системе (далее ОС) Линукс доступны несколько типов баз данных, но мы ограничимся изучением одной — PostgreSQL. Несмотря на то, что база данных MySQL имеет огромную популярность, мы выбрали одну, так как наш опыт работы с PostgreSQL значителен. Все что мы скажем в этой главе актуально и для ODBC коннекторов, в этой связи ознакомьтесь с получением вашей любимой базой данных ODBC, некоторые сущности этой главы могут использоваться Вами.
Интеграция Астериска с базами данных — один из фундаментальных элементов, позволяющих осуществить кластеризацию баз данных в Астериске в большую распределенную систему. Используя мощную базу данных, динамическое изменение данных можно перенаправлять сквозь множество систем на базе Астериска. Функция Астериска func_odbc, которую мы вспомним позже в этой главе. Не каждое развертывание Астериска будет нуждатся в реляционной базе данных, но понимание того, как использовать базы данных вместе с Астериском позволяет создать новые телекоммуникационные решения.
Инсталляция базы данных
Первое, что необходимо сделать, это проинсталировать PostgreSQL сервер базы данных:
# yum install -y postgresql-server
Следует заметить, что для промышленных телекоммуникационных систем, вы возможно пожелаете инсталировать на отдельные друг от друга разные аппаратные системы Астериск и базу данных.
Для запуска базы данных, первый запуск (инициализация) которой, может занят несколько секунд, необходимо ввести:
# service postgresql start
Следующее, что необходимо — создать пользователя с именем asterisk, который будет использоваться для соединения и управления базой данных. Запустите следующие команды:
# su - postgres
$ createuser -P
to add: asterisk
Enter name of user
Enter password for new user:
Enter it again:
be a superuser? (y/n) n
Shall the new role
be allowed to create databases? (y/n) y
Shall the new user
be allowed to create more new users? (y/n) n
Shall the new user
CREATE USER
По умолчанию, PostgreSQL не прослушивает стек TCP/IP соединения, которое используется в Астериске. Мы должны модифицировать /var/lib/pgsql/data/postgresql.conf файл для того, чтобы разрешить Астериску соеденится с базой данных по протоколу IP. Чтобы это сделать, просто удалите комментарий в строках tcpip_socket и параметр порта — port. Убедитесь в том, что значение tcpip_socket изменено с false на true.
Далее, необходимо отредактировать файл /var/lib/pgsql/data/pg_hba.conf, чтобы разрешить ранее заведенному пользователю asterisk соеденятся с PostgreSQL серверу по TCP/IP. В конце этого файла, замените # Put your actual configuration here на следующее:
host all asterisk 127.0.0.1 255.255.255.255 md5
local all asterisk trust
Таким образом, мы создали базу данных, именуемую asterisk, c правом владельца asterisk. Эту базу мы будем использовать в этой главе.
$ createdb - -owner=asterisk asterisk
CREATE DATABASE
Перегрузите PostgreSQL сервер, после того как перешли от пользователя postrgres назад к пользователю root:
$exit
$service postgresql restart
Теперь мы можем проверить соединение с PostgreSQL сервером, посредством TCP/IP стека:
#psql -h 127.0.0.1 -U asterisk Password:
Welcom to psql 7.4.16, the PostgreSQL interactive terminal.
Type: \copyright for distribution terms
\ h for help with SQL commands
\? for help on internal slash commands
\g or terminate with semicolon to execute query
\q to quit
asterisk ⇒
В случае возникновения ошибок, повторно проверте Вашу конфигурацию, как было описано ранее. Вывод нижеуказанного сообщения, означет что TCP/IP соединения не разрешено:
psql: could not connect to server: Connection refused
IIs the server running on host "127.0.0.1" and accepting
TCP/IP connections on port 5432?
Инсталяция и конфигурирование ODBC
ODBC коннектор — это уровень абстрактного взаимодействия БД, что делает возможным соединить Астериск с множеством баз данных без дополнительных ресурсных затрат на создание коннекторов для той или иной выбраной БД. Это позволяет избежать дополнительных затрат для разработчика на написания кода для коннектора к БД. Использование ODBC вносит небольшую нагрузку на систему, так как мы создаем дополнительный программный уровень взаимодействия между БД и Астериском. Тем не мение, эти затраты несоизмеримы когда необходимо создать мощную и гибкую систему взаимодействия между Астериском и БД.
Перед инсталляцией коннектора в Астериске, нам необходимо инсталлировать ODBC в Линуксе. Для инсталляции ODBC драйверов, необходимо просто запустить команду:
# yum install -y unixODBC unixODBC-devel libtool-ltdl libtool-ltdl-devel
Необходимость установки unixODBC-devel пакета вызвана тем, что Астериск создает ODBC модули, которые мы будем использовать в этой главе.
Проверте, что PostgreSQL драйвер скрнфигурирован в /etc/odbcinst.ini файле. Он должен выглядеть примено так:
[PostgreSQL]
Description = ODBC for PostgreSQL
Driver = /usr/lib/libodbcpsql.so
Setup = /usr/lib/libodbcpsqlS.so
FileUsage? = 1
Далее проверьте, что ОС увидела драйвер посредством команды:
# odbcinst -q -d
Результатом успешной загрузки драйверов будет вывод названия базы PostgreSQL, как указанно ниже:
# odbcinst -q -d
[PostgreSQL]
После всего проделанного, приступим к конфигурировании /etc/odbc.ini файла. Этот файл используется для создания идентификатора, который Астериск будет использовать для связи с этой конфигурацией. Если в будущем с любой точки Вам необходимо изменить что-либо в БД, необходимо просто переконфигурировать этот файл, что позволит Астериску продолжить с того же места*.
[asterisk-connector]
Description = PostgreSQL connection to 'asterisk' database
Driver = PostgreSQL
Database = asterisk
Servername = localhost
UserName = asterisk
Password = welcome
Port = 5432
Protocol = 7.4
ReadOnly = No
RowVersioning = No
ShowSystemTables = No
ShowOidColumn = No
FakeOidIndex = No
ConnSettings =
* Примечание. На самом деле, Вам необходимо сменить лишь Driver, Database и Servername. Каждые username и password определены в другом месте, как будет показано ниже.
Давайте проверим, что мы можем соединится с нашей БД, используя isql-приложение. isql-приложение не выполняет соединение от пользователя root, поэтому необходимо его запустить от пользователя владельца БД (пр. перев. — в нашем случае asterisk). Исходящее соединение с ОС Линукс должно быть запущено от пользователя asterisk. В главе 14 мы расcкажем, как запустить Астериск от non-root пользователя.
# su - asterisk
$ echo "select 1" | isql -v asterisk-connector
+———————————————————-+
| Connected! |
| |
| sql-statement |
| help [tablename] |
| quit |
| |
+———————————————————-+
SQL> +——————+
| ?column? |
+——————+
| 1 |
+——————+
SQLRowCount returns 1
1 rows fetched
$ exit
После того как мы проинсталлировали, сконфигурировали и проверили работоспособность unixODBC драйвера, необходимо пересобрать Астериск с поддержкой модулей ODBC. Для этого необходимо вернутся к исходникам Астериска и в каталоге установки Астериска запустить конфигурационный скрипт ./configure. Скрипт проверит наличие проинсталлированного unixODBC драйвера:
# cd /usr/src/asterisk-1.4
# make distclean
# ./configure
# make menuselect
# make install
Важно! Практически все инсталляции в этой главе запущены по умолчанию. Можно запустить скрипт make menuselect для подтверждения того, что ODBC модули доступны.Модули включают cdr_odbc, func_odbc, func_realtime, pbx_realtime, res_config_odbc, res_odbc. Для хранения голосовой почты в базе данных ODBC, необходимо выбрать в закладке меню "Voicemail Build Options" параметр ODBC_STORAGE. Мы также можем убедиться в существовании файлов в каталоге /usr/lib/
asterisk/modules/.
Конфигурация res_odbc.conf для доступа к БД
Все ODBC коннекторы конфигурируются в /etc/asterisk/res_odbc.conf файле, где можно установить все параметры настройки для различных модулей Астериска, используемые для соединения с БД.
Отредактируем файл res_odbc.conf, как показано ниже:
[asterisk]
enabled ⇒ yes
dsn ⇒ asterisk-connector
username ⇒ asterisk
password ⇒ welcom
pooling ⇒ no
limit ⇒ 0
pre-connect ⇒ yes
dsn — это опции, связанные с соединением с базой данных, которую мы сконфигурировали в файле /etc/odbc.ini. Опция pre-connect говорит Астериску открыть и подтвердить соединение с БД, когда загружается модуль res_odbc.so
После конфигурирования файла res_odbc.conf, запустите Астериск и проверьте соединение Астериск и БД с помощью команды odbc show:
*CLI>odbc show
[Name:asterisk Name: asterisk]
DSN: asterisk-connector
Опции pooling и limit необходимо использовать для MS SQL Server и Sybase базы данных. Это позволит Ваш осуществлять множество соединений (limit численно устанавливает это ограничение) к базе данных, вместе с тем убедитесь в том, что каждое соединение может иметь только одну выполняющуюся заявку в каждом соединении (это реализовано благодаря ограничениям в протоколе, который используется этими серверами БД).
Pooled:no''[[BR]]''Connected: yes
Использование Realtime
Asterisk Realtime Architecture (ARA) — Архитектура Реального Времени Астериска, метод хранения конфигурационных файлов (которые обычно находятся в каталоге /etc/asterisk/), опции которого могут хранится в таблице БД. Существует два типа realtime: static (статический) и dynamic (динамический). Статическая версия это просто классический метод чтения (пр. перевод. text parsing — парсинг текста), за исключением того что данные считываются с БД. Динамический метод используется подобно user и peer (для каналов SIP,IAX2), голосовой почты, которые загружаются и обновляют информацию по необходимости. Изменения статической информации требует перезагрузки, также как и изменения текстового файла в ОС, а динамическая информация, запрашиваемая Астериском по необходимости, не требует перезагрузки. Режим реального времени конфигурируется в etc/asterisk/extconfig.conf. Этот файл сообщает Астериску, что и когда будет загружено с БД, точно определить загружаемые файлы с БД и другие файлы, загружаемые со стандартных конфигурационных файлов.
Статическая архитектура реального времени
Статический realtime используется тогда, когда Вы хотите хранить информацию с конфигурационных файлов /etc/asterisk/, но загружать ее с БД. Можно без проблем использовать классические конфигурационные файлы в static realtime, подобно тому, как мы используем в командной строке Астериска CLI загрузку/перезагрузку модулей, связанных с соответствующими конфигурационными файлами (например, module reload chan_sip.so)
Использование директивы предзагрузки (pre-load)
Большинство файлов могут быть загружены посредством метода static realtime, но некоторые файлы не могут быть загружены используя этот метод. К таким файлам относят: asterisk.conf, extconfig.conf и logger.conf. Добавим, что файлы manager.conf, cdr.conf, rtp.conf не могут быть загружены с помощью static realtime, за исключением драйверов баз данных. Последние загружаются до загрузки главного ядра Астериска — это обьясняется тем, что конфигурационные данные необходимо загрузить до того как модуль прочитает эту конфигурацию.
Ввиду того, что мы используем ODBC драйвер в этой главе, нам необходимо добавить следующие строки в /etc/asterisk/modules.conf:
; /etc/asterisk/modules.conf
preload ⇒ res_odbc.so
preload ⇒ res_config_odbc.so
Когда мы используем static realtime, мы сообщаем Астериску, какие файлы мы хоти загрузить с базы данных. Для этого используем следующий синтаксис:
; /etc/asterisk/extconfig.conf
filename.conf ⇒ driver,database[,table]
Важно! Если имя таблицы неуказано, Астериск вместо этого будет использовать имя файла.
Модуль static realtime использует специальный формат таблицы для чтения конфигурации со статических файлов в/из базу данных. Вы можете определить таблицу для режима static realtime в PostgreSQL, как показано ниже:
CREATE TABLE ast_config
(
id serial NOT NULL,
cat_metric int4 NOT NULL DEFAULT 0,
var_metric int4 NOT NULL DEFAULT 0,
filename varchar(128) NOT NULL DEFAULT ''::character varying,
category varchar(128) NOT NULL DEFAULT 'default'::character varying,
var_name varchar(128) NOT NULL DEFAULT ''::character varying,
var_val varchar(128) NOT NULL DEFAULT ''::character varying,
commented int2 NOT NULL DEFAULT 0,
CONSTRAINT ast_config_id_pk PRIMARY KEY (id)
)
WITHOUT OIDS;
Коротко говоря, эти колонки необходимы для того, чтобы понять как Астериск получает строки и применяет их для конфигурации различных модулей, выможете загрузить:
cat_metric: Означает вес (пр. перев.: место) категории в файле. Чем ниже значение, тем оно выше (ранее) возникло в файле (см. главу "Несколько слов о метриках").
var_metric: Вес значения внутри категории . Чем ниже значение метрики, тем раньше оно возникло в файле. Это часто используется при определении порядка кодеков в файлах sip.conf, iax.conf, когда вы хотите изначально запретить использование кодеков опцией disallow=all (метрика 0), далее может следовать allow=ulaw (метрика 1), далее allow=gsm (метрика 2).
filename: Это модуль, который обычно читает с жесткого диска Вашей операционной системы (например, musiconhold.conf, sip.conf,iax.conf, и т. п.).
category: Имя секции внутри файла, подобно секции [general], но не сохраняйте в базе данных, используя квадрадтные скобки.
var_name: Значение выражения, находящееся с левой стороны (например, в выражении disallow=all, var_name — это disallow).
var_val: Значение выражения, находящееся с правой стороны (например, в выражении disallow=all, var_val — это all).
commented: Любое значение отличное от нуля может быть оценено, как если бы оно было присоединено с помощью точки с запятой в теле файла (раскомментировано)
Коротко о метриках
Метрики в static realtime используются для контроля порядка в котором объекты будут считываться в память. Предполагают, что cat_metric и var_metric соответствуют оригинальным номерам строк в файле. Самое верхнее поле cat_metric обрабатывается первым (потому как Астериск обрабатывает категории снизу вверх — это и есть причиной, по которой порядок пользователей users и peers имеет значение в sip.conf или iax.conf). После, первой будет обработана категория var_metric, потому как Астериск внутри категории обрабатывает значения сверху вниз (например, disallow=all должно содержать значение ниже, чем опции allow внутри категории, что гарантирует обработку disallow первым, до обработки allow).
Примечание (перевод.) cat_metric действительно обрабатывается снизу вверх. Для того, чтобы убедится в этом, можно вывести в *CLI>sip show peers. При этом порядок вывода пользователей в CLI будет обратным порядку описания (заведения) их в sip.conf
Самый простой пример загрузка static realtime — загрузка musiconhold.conf. Для этого сделаем копию файла musiconhold.conf:
# cd /etc/asterisk
# mv musiconhold.conf musiconhold.conf.old
чтобы выгрузить из памяти этот класс, необходимо перегрузить Астериск. Чтобы убедится, что класс moh выгружен, в CLI Астериска необходимо сделать следующее:
*CLI> restart now
*CLI> moh show classes
*CLI>
Теперь можно положить класс [default] назад в файл musiconhold.conf, но теперь мы загрузим его с БД. Соединимся с PostgreSQL и выполним следующую конструкцию INSERT:
INSERT INTO ast_config (filename,category,var_name,var_val)
VALUES ('musiconhold.conf','general','mode','files');
INSERT INTO ast_config (filename,category,var_name,var_val)
VALUES ('musiconhold.conf','general','directory','/var/lib/asterisk/moh');
Теперь осталось лишь модифицировать файл etc/asterisk/extconfig.conf для того, чтобы Астериск знал, какие данные необходимо получить с БД. Для этого добавим строку в конец файла extconfig.conf, после чего сохраним изменения:
musiconhold.conf ⇒ odbc,asterisk,ast_config
После всего вышеуказанного, присоединимся консолью к серверу Астериск и выполним перегрузку:
*CLI> module reload
Теперь необходимо убедится, что класс moh загружен с БД, запустив CLI moh show classes:
*CLI> moh show classes
Class: general
Mode: files
Directory: /var/lib/asterisk/moh
Теперь класс moh загружен с БД. Можно выполнить те же шаги для загрузки другий файлов с БД.
Динамическмя архитектура реального времени (dynamic realtime)
Динамическая загрузка используется в том случае, если загружаемые объекты часто изменяются (например, SIP/IAX2 пользователи (users и peers)) очереди и их участники (members), голосовая почта. Учитывая то, что информация в системе может быть либо изменена, либо добавляться новые записи постоянно, мы можем использовать всю мочь БД, чтобы загрузить это информацию по необходимости.
Вся конфигурация происходит в /etc/asterisk/extconfig.conf, но dynamic realtime имеет строго определенные имена, так называемые sippeers. Определение подобны определению SIP пиров и имеют следующий формат:
; extconfig.conf
sippeers ⇒ driver,database[,table]
Как видно из описания, имя таблицы используется опционально, в случае, если Астериск будет использовать предопределенное имя (например, sippeers) в качестве таблицы для поиска данных. В нашем примере, мы будем использовать ast_sippeers таблицу для хранения информации о пирах (пр. перев.: peer — пользователь).
Примечание! Запомните, что мы имеем дело как с sip peer, так и с sip user; peer — это конечная точка, которой мы посылаем звонок, а user — это пользователь, которыей принимает звонок. Friend — условное обозначения связки peer+user.
Таким образом, для конфигурации Астериска осуществлять загрузку SIP пиров c БД динамически, нам необходимо определит что-то вроде этого;
; extconfig.conf
sippeers ⇒ odbc,asterisk,ast_sipfriends
Также, для загрузки наших SIP пользователей с БД, необходимо определить что-то вроде этого:
sipusers ⇒ odbc,asterisk,ast_sipfriends
Вы могли заметить, что мы использовали одну и ту же таблицу, как для sipusers, так и для sippeers. Это объясняется тем, мы имеем тип поля (только в том случае, если он определен в файле sip.conf), который дает нам возможность определить тип friend=user+peer. Когда мы определяем таблицу для SIP user и peer, нам необходимо как минимум следующее:
+———+————+———-+————+——-+——————+—————+
|name |host |secret | ipaddr | port| regseconds | username |
+———+————+———-+————+——-+——————+—————+
|100 |dynamic |welcome| | |1096954152 | 1000 |
+———+————+———-+————+——-+——————+—————+
Столбцы port, regseconds и ipaddr необходимы для сообщения Астериску хранить регистрационную информацию для пиров с той целью, чтобы указавать, куда отправлять исходящий звонок. Так можно сделать при условии, что хост указан динамически (пр. перевод.: host=dynamic), однако, если пир указан статически, мы должны сообщить ipaddr самостоятельно в нашем поле. Поле port опционально, и может использовать стандартный порт, указанный в секции [general] и regseconds оставлять пустым. Существует множество опций для SIP friend, которые мы можем определить, например CallerID. Чтобы это сделать, просто необходимо добавить эту информацию callerid в колонку таблицы. См. sip.conf.sample файл для всех опций, которые вы можите определить для SIP friends.
Хранение Call Detail Records
Call Detail Records (далее CDR) хранит информацию о звонках, обработанных Астериском. Они (прим перевод.: записи) будут рассмотрены в главе 13. Это очень распространенный способ использовать CDR в Астериске, так как CDR'ами можно легко управлять, если мы храним записи в БД (например, нам необходимо отслеживать состояния нескольких систем на базе Астериска в одной таблице).
Установка опции systemname для Globally Unique ID (Глобалных уникальных ID, прим. Перевод.)
CDR содержит уникальный идентификатор и несколько полей информации о звонке (включая информацию о канале-источнике, канале-назначения звонка, длительность звонка последнее выполненное приложение и так далее). В случае кластерной архитектуры Астерисков, теоретически, может возникнуть ситуация дублирования уникальных идентификаторов (UID), так как каждый сервер Астериска имеет собственную систему идентификации звонков. Адресуя их, мы можем автоматически прикреплять системный идентификатор к началу UID, используя опцию в /etc/asterisk/asterisk.conf. Для каждого сервера Астериск установите уникальный идентификатор системы, например так:
[options]
systemname=toronto
Теперь, создадим таблицу в нашей БД для хранения CDR. Зарегистрируемся на сервере PostgreSQL посредством запуска приложения psql:
# psql -U asterisk -h localhost asterisk
Password:
Далее, создадим таблицу asterisk_cdr:
asterisk⇒ CREATE TABLE asterisk_cdr
(
id bigserial NOT NULL,
calldate timestamptz,
clid varchar(80),
src varchar(80),
dst varchar(80),
dcontext varchar(80),
channel varchar(80),
dstchannel varchar(80),
lastapp varchar(80),
lastdata varchar(80),
duration int8,
billsec int8,
disposition varchar(45),
amaflags int8,
accountcode varchar(20),
uniqueid varchar(40),
userfield varchar(255),
CONSTRAINT asterisk_cdr_id_pk PRIMARY KEY (id)
)
WITHOUT OIDS;
Мы можем проверить, что таблица создана, используя следующую команду \dt (описания таблиц):
asterisk⇒ \dt asterisk_cdr
List of relations
Schema | Name | Type | Owner
————+———————+———-+—————
public | asterisk_cdr | table | asterisk
(1 row)
Теперь сконфигурируем Астериск для хранения собственных CDR в БД. Это выполняется в в /etc/asterisk/cdr_odbc.conf с помощью следующей записи:
[global]
dsn=asterisk-connector
username=asterisk
password=welcome
loguniqueid=yes
table=asterisk_cdr
Если Астериск уже запущен, с *CLI> выполните следующее:
*CLI> module reload cdr_odbc.so
Мы можем также перегрузить все конфигурационный настройки Астериска с помощью:
*CLI> reload
Проверьте статус CDR, путем ввода следующей команды поиска зарегистрированных CDR точек входа:
*CLI> cdr status
CDR logging: enabled
CDR mode: simple
CDR registered backend: cdr-custom
CDR registered backend: cdr_manager
CDR registered backend: ODBC
Сейчас позвоните с помощью Астериска и убедитесь, что данные о звонке появились в таблице asterisk_cdr. Самый простой способ протестировать звонок — это инициировать его с CLI Астериска:
*CLI> console dial (убедитесь в то, что вы имеете звуковую карту и проинсталлированный драйвер chan_oss). Таким образом, вы можете проверить люьой метод с помощью вызова с консоли:
*CLI> console dial 100@default
— Executing [100@default:1] Playback("OSS/dsp", "tt-weasels") in new stack
— <OSS/dsp> Playing 'tt-weasels' (language 'en')
Далее, соединитесь с БД и выполните запрос SELECT, чтобы удостоверится в том, что
Для этого выполните запрос SELECT * FROM asterisk _cdr; но таким образом мы получим немного больше данных:
# psql -U asterisk -h localhost asterisk
Password:
asterisk⇒ SELECT id,dst,channel,uniqueid,calldate FROM asterisk_cdr;
id | dst | channel | uniqueid | calldate
——+——-+————-+———————————+————————————
1 | 100 | OSS/dsp | toronto-1171611019.0 | 2007-02-16 !02:30:19-05
(1 rows)
Удивительная функция func_odbc: Hot-Desking
Функция func_odbc — наиболее мощное средство из всех функций доступных в номерном плане Астериска. Функция позволяет создавать и использовать простые функции номерного плана, которые отыскивают и используют информацию с БД прямо в номерном плане. Существует несколько вариантов использования, например, управление пользователями или делать доступной динамическую информацию внутри Астерисков, заведенных в кластер. Что func_odbc позволяет Вам выполнить — определяется SQL-запросами, которым вы назначаете имя функций. По сути, вы создаете пользовательские функции, которые возвращают результат выполнения запроса назад в БД. Файл func_odbc.conf содержит связь между созданными Вами именами функций и SQL-конструкциями, которые будут выполнены по факту вызова ново-спеченных функций. Посредством именных ссылок в номерном плане мы можем найти и обновить значение в БД.
Примечание! Используя внешние скрипты для взаимодействия с БД (с их помощью создается файл, который потом будет прочитан Астериском) имеет преимущество (если БД была записана, Ваша система продолжает функционировать и скрипт не обновит не один файл до тех пор, пока не будет восстановлена возможность связи с БД). Главный недостаток в том, что любое изменение, сделанное пользователем не доступно до тех пор, пока не будет запущен скрипт. Возможно, что этот факт не совсем важен в маленьких системах, но в больших промышленных решениях, ожидание вступления в силу изменений может быть причиной, например, во время паузы при осуществлении звонку, в то время как загружается и проверяется большой файл. Вы можете решить это с помощью репликаций БД. Начиная с версии 1.4. Астериск (и до текущей) синтаксис файла /etc/asterisk/func_odbc.conf изменился незначительно, но получена возможность преодоления отказа (отказоустойчивость) и перераспределением ролей основного и дублирующего узлов по отношению к другой БД, используя модель master-master (pgcluster; Slony-II), или модель master-slave (Slony-I) систем репликации.
Чтобы дать Вам правильную картинку о том, что мы сказали, мы хотим Вам показать картинку Dagwood sandwich* (прим. Перевод.: http://en.wikipedia.org/wiki/Dagwood_sandwich).
* Dagwood sandwich — огромный, многослойный сэндвич, сделанный с огромного количества мяса, сыра и специй. Он был назван в честь Dagwood Bumstead, вымышленного персонажа, популярного в комиксах, который собственно и делал очень часто огромные сэндвичи. В сегодняшнем понимании Dagwood sandwich означает пережиток прошлого, который включает большое количество вареного мяса, ломтиков сыра с добавлением кусочков хлеба.
Можете ли вы отвлечься от житейского опыта, например от демонстрации на картинке томатов, или от ломтика сыра? Не тяжело? Этот загадочный пример (прим. Перевод.: действительно очень загадочный!!!
) демонстрирует попытку дать Вам практичный пример того, на сколько функция fucn_odbc мощная. Таким образом, мы решили построить полный сэндвич для Вас. Это только часть пирога, но после, нескольких укусов, арахис лучше чем желе и никогда не будет тем же самым (прим. Перевод.: ну америкоса понесло….)
Например, мы хотим что-то реализовать такое, что для нас будет полезным. Продемонстрируем небольшую компанию, которая состоит из 5 менеджеров по продажам. Они делят между собой два стола. Это страшно неудобно, потому как их семья расходует большинство времени на дорогу. Сами же менеджеры в неделю находятся от силы один день в офисе. Тем не менее, когда они работают в офисе, система знает, какой рабочий стол они занимают, таким образом, их звонки могут быть перенаправлены непосредственно туда. Таким образом, руководитель может иметь возможность отслеживать, когда они находятся в офисе и регулировать привилегии осуществления вызовов с их телефонов, когда никого там (прим. Перевод.: в офисе) нет.
Это решается типичным способом, что мы называем hot-desking возможность. Это можно построить, как будет показано ниже, с помощью вызова мощной функции func_odbc.
Создадим два настольных телефона в sip.conf:
; sip.conf
; HOT DESK USERS
[desk_1]
type=friend
host=dynamic
secret=my_special_secret
context=hotdesk
qualify=yes
[desk_2]
type=friend
host=dynamic
secret=my_special_secret
context=hotdesk
qualify=yes
; END HOT DESK USERS (конец описания hot-desktop пользователей)
Оба телефона входят в контекст [hotdesk] номерного плана (extensions.conf). Если мы действительно хотим создать эти два телефона, нам необходимо настроить эти параметры на соответствующих телефонах, об этом шла речь в главе 4.
Это все, что необходимо для sip.conf. Мы получили два куска хлеба. Но это едва ли можно назвать сэндвичем.
Использование функции ARRAY()
В нашем примере мы использовали две отдельные БД звонков и поместили их значение в пару канальных переменных, (${E}_STATUS и ${E}_PIN). Это можно просто выполнить так:
exten ⇒ _110[1-5],n,Set(${E}_STATUS=${HOTDESK_INFO(status,${E})})
exten ⇒ _110[1-5],n,Set(${E}_PIN=${HOTDESK_INFO(pin,${E})})
Как альтернативный способ, мы можем вернуть несколько колонок в несколько переменных, используя массив ARRAY() — функцию номерного плана. Если мы предопределили SQL-конструкцию в файле func_odbc.conf так:
read=SELECT pin,status FROM ast_hotdesk WHERE extension = '${E}'
мы можем использовать функцию ARRAY() для хранения каждой колонки информации на ряду с переменными с помощью одного обращения к БД:
exten ⇒ _110[1-5],n,Set(ARRAY(${E}_PIN,${E}_STATUS)=${HOTDESK_INFO(${E})})
Таким образом, в первые линии нашего блока кода мы передали значение status и значение, содержащееся в переменной ${E}, (например 1101) в функцию HOTDESK_INFO(). После, два эти значения перенесены в SQL-конструкцию с ${ARG1} и ${ARG2}, соответственно, после выполнения SQL-конструкции, возвращаемое значение назначили в канальную переменную ${E}_STATUS.
Теперь давайте закончим написание маски:
exten ⇒ _110[1-5],n,Set(${E}_STATUS=${HOTDESK_INFO(status,${E})})
exten ⇒ _110[1-5],n,Set(${E}_PIN=${HOTDESK_INFO(pin,${E})})
exten ⇒ _110[1-5],n,GotoIf($[${ISNULL(${${E}_STATUS})}]?invalid_user,1)
; check if ${E}_STATUS is NULL — проверка переменной на предмет нулевого значения
exten ⇒ _110[1-5],n,GotoIf($[${${E}_STATUS} = 1]?logout,!1:login,1)
После, значение переменной, находящейся в колонке status, помещается в канальную переменную ${E}_STATUS (если вы набираете 1101, имя переменной может быть 1101_STATUS), мы проверяем если мы получили значение назад с БД (проверка на ошибку).
Самая последняя строка в блоке проверяет cтатус телефона и, если в в состоянии «зарегистрирован», переводит в состояние «не зарегистрирован». Если еще не зарегистрирован, эта строка в блоке кода переводит в экстеншен login с приоритетом 1 в том же контексте.
Примечание! Начиная с версии Астериска 1.4 (и до текущей), Вы можете использовать канальную переменную ${ODBCROWS}, выполняя конструкцию через readsql. Мы можем перенести GotoIf() так:
exten ⇒ _110[1-5],n,GotoIf($[${ODBCROWS} < 0]?invalid_user,1)
Экстеншен login запускает инициализацию проверки ввода пин-кода агентом. Мы позволим агенту сделать три попытки ввода пин-кода и, если он введен неверно, мы отсылаем звонок в экстеншен login_fail (который мы напишем позже):
exten ⇒ login,1,NoOp() ; set counter initial value
exten ⇒ login,n,Set(PIN_TRIES=0) ; set max number of login attempts
exten ⇒ login,n,Set(MAX_PIN_TRIES=3)
exten ⇒ login,n(get_pin),NoOp() ; increase pin try counter
exten ⇒ login,n,Set(PIN_TRIES=$[${PIN_TRIES} + 1])
exten ⇒ login,n,Read(PIN_ENTERED|enter-password|${LEN(${${E}_PIN})})
exten ⇒ login,n,GotoIf($[${PIN_ENTERED} = ${${E}_PIN}]?valid_login,1)
exten ⇒ login,n,Playback(invalid-pin)
exten ⇒ login,n,GotoIf($[${PIN_TRIES} ⇐${MAX_PIN_TRIES}]?get_pin:login_fail,1)
Примечание! Запомните, что в традиционных телефонных системах номер должен состоять из цифр, но в Астериске можно именовать его, как угодно. Преимущество такого использование экстеншена заключается в том, что пользователь не может набирать номер символьный номер, что более безопасно. Мы используем несколько символьных экстеншенов в нашем примере. Если Вы хотите быть абсолютно уверенны в том, что пользователь злонамеренно не воспользуется доступом к символьному экстеншену, просто используйте одну хитрость языка программирования в Астериске (AEL), который зашоужает пользователей; начните с приоритете, отличного от единицы.
Если введенный пин-код принят, мы подтверждаем вход с помощью экстеншена valid_login. Сначала мы использовали канальную переменную CHANNEL, чтобы вычислить какой телефон сейчас осуществляет вызов. Обычно канальная переменная CHANNEL содержит запись, подобную этой — SIP/desk_1-ab4034c, таким образом мы можем использовать функцию cut() к первой части SIP-строки и определить расположение LOCATION. После того, как мы обрезали -ab4034c. Избавьтесь от нее и назначьте тому, что осталось от усечения — переменной LOCATION.
exten ⇒ valid_login,1,NoOp()
; CUT off the channel technology and assign to the LOCATION variable
; вырезаем канальный идентификатор и помещаем его значение в переменную LOCATION
exten ⇒ valid_login,n,Set(LOCATION=${CUT(CHANNEL,/,2)})
; CUT off the unique identifier and save the remainder to the LOCATION variable
; вырезмаем уникальный идентификатор и оставшееся помещаем в переменную LOCATION
exten ⇒ valid_login,n,Set(LOCATION=${CUT(LOCATION,-,1)})
Мы еще не использовали пользовательскую функцию HOTDESK_CHECK_PHONE_LOGINS(), созданную в файле func_odbc.conf и проверяет любого пользователя на предмет регистрации на телефоне и если он забыл выйти со своей учетной записи (log out). Если номер зарегистрированного пользователя был больше ноля (а это должен быть 1, но мы так или иначе проверили и перегрузили это тоже). Это запущено в логике экстеншена logout_login.
Если ни один из агентов не был зарегистрирован, мы обновили статус регистрации для этого пользователя с помощью функции HOTDESK_STATUS():
exten ⇒ valid_login,n,Set(ARRAY(USERS_LOGGED_IN)=${HOTDESK_CHECK_PHONE_
LOGINS(${LOCATION})})
exten ⇒ valid_login,n,GotoIf($[${USERS_LOGGED_IN} > 0]?logout_login,1)
exten ⇒ valid_login,n(set_login_status),NoOp()
; Set the status for the phone to '1' and where we're logged into
; Устанавливаем статус для телефона в значение «1»
и где мы зарегистрировались
; NOTE: we need to escape the comma here because the Set() application has arguments
; Заметьте, что нам необходимо здесь избегать запятой, потому как приложение Set() имее аргументы
exten ⇒ valid_login,n,Set(HOTDESK_STATUS(${E})=1\,${LOCATION})
exten ⇒ valid_login,n,GotoIf($[${ODBCROWS} < 1]?error,1)
exten ⇒ valid_login,n,Playback(agent-loginok)
exten ⇒ valid_login,n,Hangup()
В файле func_odbc.conf напишем следующее:
[STATUS]
prefix=HOTDESK
dsn=asterisk
write=UPDATE ast_hotdesk SET status = '${VAL1}', location = '${VAL2}' WHERE extension
= '${ARG1}'
Синтаксис очень прост для чтения и уже обсуждался ранее в этой главе, но есть несколько вещей, которые мы обсудим, перед тем как двигаться дальше.
Первое, на что необходимо обратить внимание, что мы используем обе переменные ${VALx} и ${ARGx} в нашей SQL-конструкции. Эти переменные содержать значение, которые мы передаем в функцию с номерного плана. В нашем случае, мы имеем дело с двумя переменными и одним аргументом ARG, которую была установлена с номерного плана посредством конструкции:
Set(HOTDESK_STATUS(${E})=1\,${LOCATION})
Примечание! В связи с тем, что приложение номерного плана Set() также может иметь аргументы (Вы можете установить множество переменных со значением, разделяя их запятой или «трубой»
| Вам необходимо избавиться от запятой с обратным слешем (\), тогда переменные не будут обработаны программой синтаксического анализа выражений для приложения Set(), но скорей всего проверит их с помощью функции HOTDESK_STATUS(). Следует отметить, что синтаксис незначительно отличается от того, в котором мы привыкли читать. Это сообщает Астериску тот факт, что Вы хотите выполнить запись (такой же синтаксис, как и в синтаксис других функций номерного плана). Мы передаем значение переменной ${E} в функцию HOTDESK_STATUS(), значение которой после доступно для SQL-конструкции, содержащуюся в func_odbc.conf с переменной ${ARG1}. Мы после этого передаем два значения:1 и ${LOCATION}. Это становится доступным для SQL-конструкции ${VAL1} и ${VAL2} переменных, соответственно. Как было уже сказано ранее, если мы имели log out для одного или нескольких агентов то того как они log in, мы должны проверить их с помощью символьного экстеншена logout_login. Такая номерная логи используется в приложением While() в номерном плане, чтобы зациклить БД и выполнить какую-либо коррекцию в БД, что может случиться. Вероятней всего выполнится только один цикл, но это хороши пример того, как можно обновить или проверить синтаксис нескольких строк записи в БД: exten ⇒ logout_login,1,NoOp() ; set all logged in users on this device to logged out status ${USERS_LOGGED_IN} exten ⇒ logout_login,n,Set(ROW_COUNTER=0) exten ⇒ logout_login,n,While($[${ROW_COUNTER} < $ {USERS_LOGGED_IN}]) Переменная ${USERS_LOGGED_IN} была предварительно установлена с помощью функции HOTDESK_CHECK_PHONE_LOGINS() , которой присвоено значение 1 или больше. Мы сделали это путем подсчета количества строк, которые были затронуты: ; func_odbc.conf [CHECK_PHONE_LOGINS] prefix=HOTDESK dsn=asterisk read=SELECT COUNT(status) FROM ast_hotdesk WHERE status = '1' AND location = '${ARG1}' Мы после этого получили номер экстеншена пользователя, который зарегистрирован с помощью функции HOTDESK_LOGGED_IN_USER(). Переменная LOCATION заполнена значением desk_1, которая говорит нам, с каким устройством мы хотим начать работу. Переменная ${ROW_COUNTER} содержит количество проходов цикла. Обе переменные переданы в функцию номерного плана в качестве переменных. Результат работы помещается в переменную WHO: exten ⇒ logout_login,n,Set(WHO=${HOTDESK_LOGGED_IN_USER(${LOCATION},${ROW_COUNTER})}) Функция HOTDESK_LOGGED_IN_USER(), которая вытаскивает ряд строк из БД, которые, таким образом, подобно итерациям (повторам) цикла мы применяли к процессу. [LOGGED_IN_USER] prefix=HOTDESK dsn=asterisk read=SELECT extension FROM ast_hotdesk WHERE status = '1' AND location = '${ARG1}' ORDER BY id LIMIT '1' OFFSET '${ARG2}' Теперь мы знаем какой экстеншен мы хотим обновить. Мы запишем функцию HOTDESK_STATUS() и запишем 0 в колонку status, где номер экстеншена содержит значение в переменной ${WHO}, например 1101 После мы закончим цикл с помощью функции EndWhile() и возвращаемся назад в valid_login экстеншен с меткой приоритета set_login_status (как было обсуждено ранее): exten ⇒ logout_login,n,Set(HOTDESK_STATUS(${WHO})=0) ; logout phone exten ⇒ logout_login,n,Set(ROW_COUNTER=$[${ROW_COUNTER} + 1]) exten ⇒ logout_login,n,EndWhile() exten ⇒ logout_login,n,Goto(valid_login,set_login_status) ; return to logging in Один момент для Вас может быть непонятен, а именно: как использовать канальную переменную ${ODBCROWS}, которая устанавливается с помощью функции HOTDESK_STATUS(). Собственно, это переменная сообщает нам, сколько строк было обновлено в запросе SQL UPDATE, которой мы присвоили 1 (Пр. Перевод.: присвоили значение переменной ${ODBCROWS}). Если значение ${ODBCROWS} меньше 1, тогда мы имеем дело с ошибкой и обрабатываем соответственно: exten ⇒ logout,1,NoOp() exten ⇒ logout,n,Set(HOTDESK_STATUS(${E})=0) exten ⇒ logout,n,GotoIf($[${ODBCROWS} < 1]?error,1) exten ⇒ logout,n,Playback(silence/1&agent-loggedoff) exten ⇒ logout,n,Hangup() exten ⇒ login_fail,1,NoOp() exten ⇒ login_fail,n,Playback(silence/1&login-fail) exten ⇒ login_fail,n,Hangup() exten ⇒ error,1,NoOp() exten ⇒ error,n,Playback(silence/1&connection-failed) exten ⇒ error,n,Hangup() exten ⇒ invalid_user,1,NoOp() exten ⇒ invalid_user,n,Verbose(1|Hot Desk extension ${E} does not exist) exten ⇒ invalid_user,n,Playback(silence/2&invalid) exten ⇒ invalid_user,n,Hangup() Мы также включим контекст [hotdesk_outbound], который обрабатывает наши исходящие звонки после того, как агенты вошли в систему: include ⇒ hotdesk_outbound Собственно контекст [hotdesk_outbound] объединяет все набранные номера со стороны настольных телефонов. Мы первоначально установим нашу LOCATION переменную, используя переменную CHANNEL, после, определим какой номер (агент) вошел в систему и поместим это в переменную WHO. В том случае, если переменная NULL, исходящий звонок будет сброшен. В противном случае. Мы можем получить информацию об агенте с помощью функции HOTDESK_INFO() и присвоить ее нескольким CHANNEL переменным. Это включить в контекст обработки звонков, где мы выполним Goto() в контекст который ассоциирован с исходящими звонками. Если мы попытаемся выполнить набор номера, который не обрабатывается нашим контекстом (или один из транзитных контекстов, например, звонок в межгород, международный звонок и пр.), создадим экстеншен i, который будет проигрывать PlayBack сообщение в случае, если действие (набранный номер) не может быть обработан и как результат — завершение звонка. [hotdesk_outbound] exten ⇒ _X.,1,NoOp() exten ⇒ _X.,n,Set(LOCATION=${CUT(CHANNEL,/,2)}) exten ⇒ _X.,n,Set(LOCATION=${CUT(LOCATION,-,1)}) exten ⇒ _X.,n,Set(WHO=${HOTDESK_PHONE_STATUS(${LOCATION})}) exten ⇒ _X.,n,GotoIf($[${ISNULL(${WHO})}]?no_outgoing,1) exten ⇒ _X.,n,Set(${WHO}_CID_NAME=${HOTDESK_INFO(cid_name,${WHO})}) exten ⇒ _X.,n,Set(${WHO}_CID_NUMBER=${HOTDESK_INFO(cid_number,${WHO})}) exten ⇒ _X.,n,Set(${WHO}_CONTEXT=${HOTDESK_INFO(context,${WHO})}) exten ⇒ _X.,n,Goto(${${WHO}_CONTEXT},${EXTEN},1) [international] ; международные звонки, например exten ⇒ _011.,1,NoOp() exten ⇒ _011.,n,Set(E=${EXTEN}) exten ⇒ _011.,n,Goto(outgoing,call,1) exten ⇒ i,1,NoOp() exten ⇒ i,n,Playback(silence/2&sorry-cant-let-you-do-that2) exten ⇒ i,n,Hangup() include ⇒ longdistance [longdistance]; межгород, например exten ⇒ _1NXXNXXXXXX,1,NoOp() exten ⇒ _1NXXNXXXXXX,n,Set(E=${EXTEN}) exten ⇒ _1NXXNXXXXXX,n,Goto(outgoing,call,1) exten ⇒ _NXXNXXXXXX,1,Goto(1${EXTEN},1) exten ⇒ i,1,NoOp() exten ⇒ i,n,Playback(silence/2&sorry-cant-let-you-do-that2) exten ⇒ i,n,Hangup() include ⇒ local [local] ; по городу, например exten ⇒ _416NXXXXXX,1,NoOp() exten ⇒ _416NXXXXXX,n,Set(E=${EXTEN}) exten ⇒ _416NXXXXXX,n,Goto(outgoing,call,1) exten ⇒ i,1,NoOp(); обработка номера, который не попал не под одну из вышеперечисленных (контекстах) масок (или шаблонов обработки набранного номера) exten ⇒ i,n,Playback(silence/2&sorry-cant-let-you-do-that2) exten ⇒ i,n,Hangup() Если набранный номер удовлетворяет требования хотя бы один из контекстов обработки звонка (за исключением i (invalid) контекста), звонок маршрутизируется в контекст [outgoing] обработки исходящих звонков, где Caller ID имя и номер устанавливаются с помощью CALLERID() функции. Звонки далее размещаются в SIP канал, используя учетную запись service_provider в sip.conf файле: [outgoing] exten ⇒ call,1,NoOp() exten ⇒ call,n,Set(CALLERID(name)=${${WHO}_CID_NAME}) exten ⇒ call,n,Set(CALLERID(number)=${${WHO}_CID_NUMBER}) exten ⇒ call,n,Dial(SIP/service_provider/${E}) exten ⇒ call,n,Playback(silence/2&pls-try-call-later) exten ⇒ call,n,Hangup() Наш service_provider может выглядеть так: [service_provider] type=friend host=switch1.service_provider.net username=my_username fromuser=my_username secret=welcome context=incoming canreinvite=no disallow=all allow=ulaw И это все!!! Все вышеописанное можно узреть в Приложении G Сколько вещей можно сделать, используя func_odbc ? Смотрите обо всем ниже. |
Attachments
-
AFOT.doc
(195.5 KB) -
added by litnimax 3 years ago.
Завершение главы 12 (Андрей поленился освоить WikiFormatting)
