int manager_endpoints_init(void)
{
	struct stasis_topic *endpoint_topic;
	int ret = 0;

	if (endpoint_router) {
		/* Already initialized */
		return 0;
	}

	ast_register_atexit(manager_endpoints_shutdown);

	endpoint_topic = ast_endpoint_topic_all_cached();
	if (!endpoint_topic) {
		return -1;
	}

	endpoint_router = stasis_message_router_create(endpoint_topic);

	if (!endpoint_router) {
		return -1;
	}

	ret |= stasis_message_router_add(endpoint_router, ast_endpoint_state_type(), endpoint_state_cb, NULL);

	/* If somehow we failed to add any routes, just shut down the whole
	 * thing and fail it.
	 */
	if (ret) {
		manager_endpoints_shutdown();
		return -1;
	}

	return 0;
}
Exemple #2
0
/*! Forward a endpoint's topics to an app */
static struct app_forwards *forwards_create_endpoint(struct stasis_app *app,
	struct ast_endpoint *endpoint)
{
	struct app_forwards *forwards;
	int ret = 0;

	if (!app) {
		return NULL;
	}

	forwards = forwards_create(app, endpoint ? ast_endpoint_get_id(endpoint) : ENDPOINT_ALL);
	if (!forwards) {
		return NULL;
	}

	forwards->forward_type = FORWARD_ENDPOINT;
	if (endpoint) {
		forwards->topic_forward = stasis_forward_all(ast_endpoint_topic(endpoint),
			app->topic);
		forwards->topic_cached_forward = stasis_forward_all(
			ast_endpoint_topic_cached(endpoint), app->topic);

		if (!forwards->topic_forward || !forwards->topic_cached_forward) {
			/* Half-subscribed is a bad thing */
			forwards_unsubscribe(forwards);
			ao2_ref(forwards, -1);
			return NULL;
		}
	} else {
		/* Since endpoint subscriptions also subscribe to channels, in the case
		 * of all endpoint subscriptions, we only want messages for the endpoints.
		 * As such, we route those particular messages and then re-publish them
		 * on the app's topic.
		 */
		ast_assert(app->endpoint_router == NULL);
		app->endpoint_router = stasis_message_router_create(ast_endpoint_topic_all_cached());
		if (!app->endpoint_router) {
			forwards_unsubscribe(forwards);
			ao2_ref(forwards, -1);
			return NULL;
		}

		ret |= stasis_message_router_add(app->endpoint_router,
			ast_endpoint_state_type(), endpoint_state_cb, app);
		ret |= stasis_message_router_add(app->endpoint_router,
			ast_endpoint_contact_state_type(), endpoint_state_cb, app);

		if (ret) {
			ao2_ref(app->endpoint_router, -1);
			app->endpoint_router = NULL;
			ao2_ref(forwards, -1);
			return NULL;
		}
	}

	return forwards;
}
static int load_module(void)
{
	struct ao2_container *endpoints;

	router = stasis_message_router_create(ast_endpoint_topic_all_cached());
	if (!router) {
		return AST_MODULE_LOAD_FAILURE;
	}
	stasis_message_router_add(router, stasis_cache_update_type(), cache_update_cb, NULL);

	endpoints = stasis_cache_dump(ast_endpoint_cache(), ast_endpoint_snapshot_type());
	if (endpoints) {
		ao2_callback(endpoints, OBJ_MULTIPLE | OBJ_NODATA | OBJ_NOLOCK, dump_cache_load, NULL);
		ao2_ref(endpoints, -1);
	}

	return AST_MODULE_LOAD_SUCCESS;
}
Exemple #4
0
static int load_module(void)
{
	/* You can create a message router to route messages by type */
	router = stasis_message_router_create(
		ast_channel_topic_all_cached());
	if (!router) {
		return AST_MODULE_LOAD_FAILURE;
	}
	stasis_message_router_add(router, stasis_cache_update_type(),
		updates, NULL);
	stasis_message_router_set_default(router, default_route, NULL);

	/* Or a subscription to receive all of the messages from a topic */
	sub = stasis_subscribe(ast_channel_topic_all(), statsmaker, NULL);
	if (!sub) {
		return AST_MODULE_LOAD_FAILURE;
	}
	return AST_MODULE_LOAD_SUCCESS;
}
Exemple #5
0
struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *data, enum stasis_app_subscription_model subscription_model)
{
	RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
	size_t size;
	int res = 0;
	size_t context_size = strlen("stasis-") + strlen(name) + 1;
	char context_name[context_size];

	ast_assert(name != NULL);
	ast_assert(handler != NULL);

	ast_verb(1, "Creating Stasis app '%s'\n", name);

	size = sizeof(*app) + strlen(name) + 1;
	app = ao2_alloc_options(size, app_dtor, AO2_ALLOC_OPT_LOCK_MUTEX);
	if (!app) {
		return NULL;
	}
	app->subscription_model = subscription_model;

	app->forwards = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX,
		AO2_CONTAINER_ALLOC_OPT_DUPS_OBJ_REJECT,
		forwards_sort, NULL);
	if (!app->forwards) {
		return NULL;
	}

	app->topic = stasis_topic_create(name);
	if (!app->topic) {
		return NULL;
	}

	app->bridge_router = stasis_message_router_create(ast_bridge_topic_all());
	if (!app->bridge_router) {
		return NULL;
	}

	res |= stasis_message_router_add(app->bridge_router,
		ast_bridge_merge_message_type(), bridge_merge_handler, app);

	res |= stasis_message_router_add(app->bridge_router,
		ast_blind_transfer_type(), bridge_blind_transfer_handler, app);

	res |= stasis_message_router_add(app->bridge_router,
		ast_attended_transfer_type(), bridge_attended_transfer_handler, app);

	res |= stasis_message_router_add(app->bridge_router,
		stasis_subscription_change_type(), bridge_subscription_change_handler, app);

	if (res != 0) {
		return NULL;
	}
	/* Bridge router holds a reference */
	ao2_ref(app, +1);

	app->router = stasis_message_router_create(app->topic);
	if (!app->router) {
		return NULL;
	}

	res |= stasis_message_router_add(app->router,
		ast_bridge_snapshot_type(), sub_bridge_update_handler, app);

	res |= stasis_message_router_add(app->router,
		ast_channel_snapshot_type(), sub_channel_update_handler, app);

	res |= stasis_message_router_add_cache_update(app->router,
		ast_endpoint_snapshot_type(), sub_endpoint_update_handler, app);

	res |= stasis_message_router_add(app->router,
		stasis_subscription_change_type(), sub_subscription_change_handler, app);

	stasis_message_router_set_formatters_default(app->router,
		sub_default_handler, app, STASIS_SUBSCRIPTION_FORMATTER_JSON);

	if (res != 0) {
		return NULL;
	}
	/* Router holds a reference */
	ao2_ref(app, +1);

	strncpy(app->name, name, size - sizeof(*app));
	app->handler = handler;
	app->data = ao2_bump(data);

	/* Create a context, a match-all extension, and a 'h' extension for this application. Note that
	 * this should only be done if a context does not already exist. */
	strcpy(context_name, "stasis-");
	strcat(context_name, name);
	if (!ast_context_find(context_name)) {
		if (!ast_context_find_or_create(NULL, NULL, context_name, "res_stasis")) {
			ast_log(LOG_WARNING, "Could not create context '%s' for Stasis application '%s'\n", context_name, name);
		} else {
			ast_add_extension(context_name, 0, "_.", 1, NULL, NULL, "Stasis", ast_strdup(name), ast_free_ptr, "res_stasis");
			ast_add_extension(context_name, 0, "h", 1, NULL, NULL, "NoOp", NULL, NULL, "res_stasis");
		}
	} else {
		ast_log(LOG_WARNING, "Not creating context '%s' for Stasis application '%s' because it already exists\n",
			context_name, name);
	}

	ao2_ref(app, +1);
	return app;
}
Exemple #6
0
struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *data)
{
	RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
	size_t size;
	int res = 0;

	ast_assert(name != NULL);
	ast_assert(handler != NULL);

	ast_verb(1, "Creating Stasis app '%s'\n", name);

	size = sizeof(*app) + strlen(name) + 1;
	app = ao2_alloc_options(size, app_dtor, AO2_ALLOC_OPT_LOCK_MUTEX);

	if (!app) {
		return NULL;
	}

	app->forwards = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX,
		AO2_CONTAINER_ALLOC_OPT_DUPS_OBJ_REJECT,
		forwards_sort, NULL);
	if (!app->forwards) {
		return NULL;
	}

	app->topic = stasis_topic_create(name);
	if (!app->topic) {
		return NULL;
	}

	app->bridge_router = stasis_message_router_create(ast_bridge_topic_all());
	if (!app->bridge_router) {
		return NULL;
	}

	res |= stasis_message_router_add(app->bridge_router,
		ast_bridge_merge_message_type(), bridge_merge_handler, app);

	res |= stasis_message_router_add(app->bridge_router,
		ast_blind_transfer_type(), bridge_blind_transfer_handler, app);

	res |= stasis_message_router_add(app->bridge_router,
		ast_attended_transfer_type(), bridge_attended_transfer_handler, app);

	res |= stasis_message_router_set_default(app->bridge_router,
		bridge_default_handler, app);

	if (res != 0) {
		return NULL;
	}
	/* Bridge router holds a reference */
	ao2_ref(app, +1);

	app->router = stasis_message_router_create(app->topic);
	if (!app->router) {
		return NULL;
	}

	res |= stasis_message_router_add_cache_update(app->router,
		ast_bridge_snapshot_type(), sub_bridge_update_handler, app);

	res |= stasis_message_router_add_cache_update(app->router,
		ast_channel_snapshot_type(), sub_channel_update_handler, app);

	res |= stasis_message_router_add_cache_update(app->router,
		ast_endpoint_snapshot_type(), sub_endpoint_update_handler, app);

	res |= stasis_message_router_set_default(app->router,
		sub_default_handler, app);

	if (res != 0) {
		return NULL;
	}
	/* Router holds a reference */
	ao2_ref(app, +1);

	strncpy(app->name, name, size - sizeof(*app));
	app->handler = handler;
	ao2_ref(data, +1);
	app->data = data;

	ao2_ref(app, +1);
	return app;
}
int manager_confbridge_init(void)
{
	STASIS_MESSAGE_TYPE_INIT(confbridge_start_type);
	STASIS_MESSAGE_TYPE_INIT(confbridge_end_type);
	STASIS_MESSAGE_TYPE_INIT(confbridge_join_type);
	STASIS_MESSAGE_TYPE_INIT(confbridge_leave_type);
	STASIS_MESSAGE_TYPE_INIT(confbridge_start_record_type);
	STASIS_MESSAGE_TYPE_INIT(confbridge_stop_record_type);
	STASIS_MESSAGE_TYPE_INIT(confbridge_mute_type);
	STASIS_MESSAGE_TYPE_INIT(confbridge_unmute_type);
	STASIS_MESSAGE_TYPE_INIT(confbridge_talking_type);

	bridge_state_router = stasis_message_router_create(
		ast_bridge_topic_all_cached());

	if (!bridge_state_router) {
		return -1;
	}

	if (stasis_message_router_add(bridge_state_router,
			confbridge_start_type(),
			confbridge_start_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(bridge_state_router,
			confbridge_end_type(),
			confbridge_end_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(bridge_state_router,
			confbridge_join_type(),
			confbridge_join_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(bridge_state_router,
			confbridge_leave_type(),
			confbridge_leave_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(bridge_state_router,
			confbridge_start_record_type(),
			confbridge_start_record_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(bridge_state_router,
			confbridge_stop_record_type(),
			confbridge_stop_record_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(bridge_state_router,
			confbridge_mute_type(),
			confbridge_mute_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(bridge_state_router,
			confbridge_unmute_type(),
			confbridge_unmute_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(bridge_state_router,
			confbridge_talking_type(),
			confbridge_talking_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}

	channel_state_router = stasis_message_router_create(
		ast_channel_topic_all_cached());

	if (!channel_state_router) {
		manager_confbridge_shutdown();
		return -1;
	}

	if (stasis_message_router_add(channel_state_router,
			confbridge_start_type(),
			confbridge_start_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(channel_state_router,
			confbridge_end_type(),
			confbridge_end_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(channel_state_router,
			confbridge_join_type(),
			confbridge_join_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(channel_state_router,
			confbridge_leave_type(),
			confbridge_leave_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(channel_state_router,
			confbridge_start_record_type(),
			confbridge_start_record_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(channel_state_router,
			confbridge_stop_record_type(),
			confbridge_stop_record_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(channel_state_router,
			confbridge_mute_type(),
			confbridge_mute_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(channel_state_router,
			confbridge_unmute_type(),
			confbridge_unmute_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}
	if (stasis_message_router_add(channel_state_router,
			confbridge_talking_type(),
			confbridge_talking_cb,
			NULL)) {
		manager_confbridge_shutdown();
		return -1;
	}

	return 0;
}
static void consumer_wait_for(struct consumer *consumer)
{
    int res;
    struct timeval start = ast_tvnow();
    struct timespec end = {
        .tv_sec = start.tv_sec + 10,
        .tv_nsec = start.tv_usec * 1000
    };

    SCOPED_AO2LOCK(lock, consumer);

    while (!consumer->already_out) {
        res = ast_cond_timedwait(&consumer->out, ao2_object_get_lockaddr(consumer), &end);
        if (!res || res == ETIMEDOUT) {
            break;
        }
    }
}

static int remove_device_states_cb(void *obj, void *arg, int flags)
{
    struct stasis_message *msg = obj;
    struct ast_device_state_message *device_state = stasis_message_data(msg);

    if (strcmp(UNIT_TEST_DEVICE_IDENTIFIER, device_state->device)) {
        /* Not a unit test device */
        return 0;
    }

    msg = stasis_cache_clear_create(msg);
    if (msg) {
        /* topic guaranteed to have been created by this point */
        stasis_publish(ast_device_state_topic(device_state->device), msg);
    }
    ao2_cleanup(msg);
    return 0;
}

static void cache_cleanup(int unused)
{
    struct ao2_container *cache_dump;

    /* remove all device states created during this test */
    cache_dump = stasis_cache_dump_all(ast_device_state_cache(), NULL);
    if (!cache_dump) {
        return;
    }
    ao2_callback(cache_dump, 0, remove_device_states_cb, NULL);
    ao2_cleanup(cache_dump);
}

AST_TEST_DEFINE(device_state_aggregation_test)
{
    RAII_VAR(struct consumer *, consumer, NULL, ao2_cleanup);
    RAII_VAR(struct stasis_message_router *, device_msg_router, NULL, stasis_message_router_unsubscribe);
    RAII_VAR(struct ast_eid *, foreign_eid, NULL, ast_free);
    RAII_VAR(int, cleanup_cache, 0, cache_cleanup);
    RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
    int res;
    struct ast_device_state_message *device_state;

    switch (cmd) {
    case TEST_INIT:
        info->name = "device_state_aggregation_test";
        info->category = "/main/devicestate/";
        info->summary = "Tests message routing and aggregation through the Stasis device state system.";
        info->description =
            "Verifies that the device state system passes "
            "messages appropriately, that the aggregator is "
            "working properly, that the aggregate results match "
            "the expected combined devstate, and that the cached "
            "aggregate devstate is correct.";
        return AST_TEST_NOT_RUN;
    case TEST_EXECUTE:
        break;
    }

    foreign_eid = ast_malloc(sizeof(*foreign_eid));
    ast_test_validate(test, NULL != foreign_eid);
    memset(foreign_eid, 0xFF, sizeof(*foreign_eid));

    consumer = consumer_create();
    ast_test_validate(test, NULL != consumer);

    device_msg_router = stasis_message_router_create(ast_device_state_topic_cached());
    ast_test_validate(test, NULL != device_msg_router);

    ao2_ref(consumer, +1);
    res = stasis_message_router_add(device_msg_router, stasis_cache_update_type(), consumer_exec, consumer);
    ast_test_validate(test, !res);

    res = stasis_message_router_add(device_msg_router, stasis_subscription_change_type(), consumer_finalize, consumer);
    ast_test_validate(test, !res);

    /* push local state */
    ast_publish_device_state(UNIT_TEST_DEVICE_IDENTIFIER, AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE);

    /* Check cache aggregate state immediately */
    ao2_cleanup(msg);
    msg = stasis_cache_get_by_eid(ast_device_state_cache(), ast_device_state_message_type(), UNIT_TEST_DEVICE_IDENTIFIER, NULL);
    device_state = stasis_message_data(msg);
    ast_test_validate(test, AST_DEVICE_NOT_INUSE == device_state->state);

    consumer_wait_for(consumer);
    ast_test_validate(test, AST_DEVICE_NOT_INUSE == consumer->state);
    ast_test_validate(test, AST_DEVICE_NOT_INUSE == consumer->aggregate_state);
    ast_test_validate(test, 2 == consumer->event_count);
    consumer_reset(consumer);

    /* push remote state */
    /* this will not produce a new aggregate state message since the aggregate state does not change */
    consumer->sig_on_non_aggregate_state = 1;
    ast_publish_device_state_full(UNIT_TEST_DEVICE_IDENTIFIER, AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, foreign_eid);

    /* Check cache aggregate state immediately */
    ao2_cleanup(msg);
    msg = stasis_cache_get_by_eid(ast_device_state_cache(), ast_device_state_message_type(), UNIT_TEST_DEVICE_IDENTIFIER, NULL);
    device_state = stasis_message_data(msg);
    ast_test_validate(test, AST_DEVICE_NOT_INUSE == device_state->state);

    /* Check for expected events. */
    consumer_wait_for(consumer);
    ast_test_validate(test, AST_DEVICE_NOT_INUSE == consumer->state);
    ast_test_validate(test, AST_DEVICE_TOTAL == consumer->aggregate_state);
    ast_test_validate(test, 1 == consumer->event_count);
    consumer_reset(consumer);

    /* push remote state different from local state */
    ast_publish_device_state_full(UNIT_TEST_DEVICE_IDENTIFIER, AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, foreign_eid);

    /* Check cache aggregate state immediately */
    ao2_cleanup(msg);
    msg = stasis_cache_get_by_eid(ast_device_state_cache(), ast_device_state_message_type(), UNIT_TEST_DEVICE_IDENTIFIER, NULL);
    device_state = stasis_message_data(msg);
    ast_test_validate(test, AST_DEVICE_INUSE == device_state->state);

    /* Check for expected events. */
    consumer_wait_for(consumer);
    ast_test_validate(test, AST_DEVICE_INUSE == consumer->state);
    ast_test_validate(test, AST_DEVICE_INUSE == consumer->aggregate_state);
    ast_test_validate(test, 2 == consumer->event_count);
    consumer_reset(consumer);

    /* push local state that will cause aggregated state different from local non-aggregate state */
    ast_publish_device_state(UNIT_TEST_DEVICE_IDENTIFIER, AST_DEVICE_RINGING, AST_DEVSTATE_CACHABLE);

    /* Check cache aggregate state immediately */
    ao2_cleanup(msg);
    msg = stasis_cache_get_by_eid(ast_device_state_cache(), ast_device_state_message_type(), UNIT_TEST_DEVICE_IDENTIFIER, NULL);
    device_state = stasis_message_data(msg);
    ast_test_validate(test, AST_DEVICE_RINGINUSE == device_state->state);

    /* Check for expected events. */
    consumer_wait_for(consumer);
    ast_test_validate(test, AST_DEVICE_RINGING == consumer->state);
    ast_test_validate(test, AST_DEVICE_RINGINUSE == consumer->aggregate_state);
    ast_test_validate(test, 2 == consumer->event_count);
    consumer_reset(consumer);

    return AST_TEST_PASS;
}

static int unload_module(void)
{
    AST_TEST_UNREGISTER(device2extenstate_test);
    AST_TEST_UNREGISTER(device_state_aggregation_test);
    return 0;
}