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; }
/*! 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; }
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; }
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; }
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; }