[Из песочницы] Кластер Asterisk. Централизация информации о регистрации

У большинства администраторов, работающих с телефонией на базе Asterisk, в компаниях, где штат превышает 500+ сотрудников, рано или поздно встает вопрос о полноценной кластеризации Active/Active. Предпосылками к этому может быть и наличие региональных ответвлений, и желание сделать систему надежнее. Тема обширная и не является целью данной статьи в полном объеме, которая написана с целью показать один из самых быстрых и надежных способов добыть информацию о регистрации устройств на серверах в кластере, с целью последующей централизации или/и дистрибуции внутри кластера. Логично предположить, что самый производительный способ — это быть частью самого Asterisk.

Поэтому, чтобы не тянуть кота за хвост, шаблон загружаемого модуля для Asterisk:
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision: $")

#include "asterisk/module.h"
#include "asterisk/logger.h"

static int load_module(void){
	// Init code here
	return AST_MODULE_LOAD_SUCCESS;
}

static int unload_module(void){
	// Destroy code here
	return 0;
}

AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Hello World");

Это минимально необходимый код, который нужно поместить в папку с сорцами Asterisk в подкаталог res. При компиляции, будет собран новый модуль с названием «как имя файла» и дискрипцией «Hello World».

Отлично, внутрь Asterisk мы попали, что дальше? Нам нужно получить информацию о регистрации телефона, бонусом мы хотим знать IP адрес телефона, возможно его джитер и статус (USE/NOT_USE/HOLD).

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

1. Подписываемся на получения событий от шины стазиса

stasis_subscribe(ast_endpoint_topic_all(),acl_change_stasis_dev_status,NULL);

ast_endpoint_topic_all () — возвращает название «Топика» сообщения.
acl_change_stasis_dev_status — Это функция, которая будет вызвана, когда в стазисе появиться нужное для нас сообщение из указанного топика.

2. Создаем функцию, в которой будем ловить нужные сообщения.
static void acl_change_stasis_dev_status(void *data, struct stasis_subscription *sub, struct stasis_message *msg){
	
	}

3. Собственно самое вкусное — код обработки события.
Перед тем как начеркать свое, нужно понять, а в каком виде оно к нам придет? Для этого лезем в сорцы и находим вот такой кусок:
ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob);

и далее:
void ast_endpoint_blob_publish(struct ast_endpoint *endpoint, struct stasis_message_type *type, struct ast_json *blob)
{
	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
	if (blob) {
		message = ast_endpoint_blob_create(endpoint, type, blob);
	}
	if (message) {
		stasis_publish(ast_endpoint_topic(endpoint), message);
	}
}

struct stasis_message *ast_endpoint_blob_create(struct ast_endpoint *endpoint,
	struct stasis_message_type *type, struct ast_json *blob)
{
	RAII_VAR(struct ast_endpoint_blob *, obj, NULL, ao2_cleanup);
	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);

	if (!type) {
		return NULL;
	}
	if (!blob) {
		blob = ast_json_null();
	}

	if (!(obj = ao2_alloc(sizeof(*obj), endpoint_blob_dtor))) {
		return NULL;
	}

	if (endpoint) {
		if (!(obj->snapshot = ast_endpoint_snapshot_create(endpoint))) {
			return NULL;
		}
	}

	obj->blob = ast_json_ref(blob);

	if (!(msg = stasis_message_create(type, obj))) {
		return NULL;
	}

	ao2_ref(msg, +1);
	return msg;
}

Что дает нам это кусок кода? Понимание того, что в качестве полезной нагрузки в нашу функцию мы получим ast_endpoint_blob, внутри которого будет ast_endpoint_snapshot и JSON.

Теперь наш код:

	if(ast_endpoint_state_type() != stasis_message_type(msg))return; // Проверим, мы получили нужное нам сообщение?
	
	// Все остальное выдергиваеться из исходных кодов Asterisk при длительном и внимательном изучении
	struct ast_endpoint_blob * n=stasis_message_data(msg); // Полезная нагрузка в данном топике ходит в виде структуры ast_endpoint_blob
	
	// Сведения о регистрации конвентированы в JSON
	struct ast_json * m;
	
    struct ast_endpoint_snapshot *snap; // А вот информация о пире лежит рядом c JSON в структуре ast_endpoint_snapshot
    snap=n->snapshot; // Получаем информацию о пире
    m=n->blob; // Получаем JSON
	
	char buffer[1050];
	sprintf(buffer,"Device %s (%s): %s\n",snap->id,ast_endpoint_state_to_string(snap->state),ast_json_dump_string_format(m,AST_JSON_COMPACT));
	ast_log(LOG_NOTICE,buffer); // И выводим в лог	

Описанные выше структуры потянут за собой следующие инклуды:
	#include "asterisk/stasis_endpoints.h"
	#include "asterisk/stasis.h"
	#include "asterisk/stasis_message_router.h"
	#include "asterisk/stasis_channels.h"
	#include "asterisk/stasis_bridges.h"
	#include "asterisk/stasis_system.h"
	#include "asterisk/devicestate.h"
	#include "asterisk/json.h"

Про них тоже нужно не забыть, при создании модуля.

Итого:

#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision: $")

#include "asterisk/module.h"
#include "asterisk/logger.h"

#include "asterisk/stasis_endpoints.h"
#include "asterisk/stasis.h"
#include "asterisk/stasis_message_router.h"
#include "asterisk/stasis_channels.h"
#include "asterisk/stasis_bridges.h"
#include "asterisk/stasis_system.h"
#include "asterisk/devicestate.h"
#include "asterisk/json.h"

static void acl_change_stasis_dev_status(void *data, struct stasis_subscription *sub, struct stasis_message *msg){
	if(ast_endpoint_state_type() != stasis_message_type(msg))return;
	
	struct ast_endpoint_blob * n=stasis_message_data(msg);
	
	struct ast_json * m;
    struct ast_endpoint_snapshot *snap;
    snap=n->snapshot;
    m=n->blob;
	
	char buffer[1050];
	sprintf(buffer,"Device %s (%s): %s\n",snap->id,ast_endpoint_state_to_string(snap->state),ast_json_dump_string_format(m,AST_JSON_COMPACT));
	ast_log(LOG_NOTICE,buffer);
}

static int load_module(void){
	stasis_subscribe(ast_endpoint_topic_all(),acl_change_stasis_dev_status,NULL);
	return AST_MODULE_LOAD_SUCCESS;
}

static int unload_module(void){
	// Тут нужно отписаться от события
	return 0;
}

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

И, собственно, как это выглядит:

image

Комментарии (0)

© Habrahabr.ru