From dd48d60c5ba239f76b054b1bb6c1e17c68537497 Mon Sep 17 00:00:00 2001 From: Mark Michelson Date: Wed, 30 Mar 2016 17:01:28 -0500 Subject: ARI: Add method to create a new channel. This adds a new ARI method to the channels resource that allows for the creation of a new channel. The channel is created and then placed into the specified Stasis application. This is different from the existing originate method that creates a channel, dials it, and then places the answered channel into the dialplan or a Stasis application. This method does not attempt to call the channel at all. Dialing is left as a later step after channel creation. This allows for pre-dialing channel manipulation if desired. ASTERISK-25889 Change-Id: I3c96a0aba914b08e39f6256371a5bd4c92cbded8 --- CHANGES | 8 +++ res/ari/resource_channels.c | 109 +++++++++++++++++++++++++++++++ res/ari/resource_channels.h | 34 ++++++++++ res/res_ari_channels.c | 138 +++++++++++++++++++++++++++++++++++++++- rest-api/api-docs/channels.json | 62 ++++++++++++++++++ 5 files changed, 349 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index f44df3284..b65225642 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,14 @@ --- Functionality changes from Asterisk 13 to Asterisk 14 -------------------- ------------------------------------------------------------------------------ +ARI +----------------- + * A new ARI method has been added to the channels resource. "create" allows for + you to create a new channel and place that channel into a Stasis application. This + is similar to origination except that the specified channel is not dialed. This + allows for an application writer to create a channel, perform manipulations on it, + and then delay dialing the channel until later. + Applications ------------------ diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c index 297560175..1954d6bf9 100644 --- a/res/ari/resource_channels.c +++ b/res/ari/resource_channels.c @@ -1461,3 +1461,112 @@ void ast_ari_channels_snoop_channel_with_id(struct ast_variable *headers, args->snoop_id, response); } + +struct ari_channel_thread_data { + struct ast_channel *chan; + struct ast_str *stasis_stuff; +}; + +static void chan_data_destroy(struct ari_channel_thread_data *chan_data) +{ + ast_free(chan_data->stasis_stuff); + ast_hangup(chan_data->chan); + ast_free(chan_data); +} + +/*! + * \brief Thread that owns stasis-created channel. + * + * The channel enters into a Stasis application immediately upon creation. In this + * way, the channel can be manipulated by the Stasis application. Once the channel + * exits the Stasis application, it is hung up. + */ +static void *ari_channel_thread(void *data) +{ + struct ari_channel_thread_data *chan_data = data; + struct ast_app *stasis_app; + + stasis_app = pbx_findapp("Stasis"); + if (!stasis_app) { + ast_log(LOG_ERROR, "Stasis dialplan application is not registered"); + chan_data_destroy(chan_data); + return NULL; + } + + pbx_exec(chan_data->chan, stasis_app, ast_str_buffer(chan_data->stasis_stuff)); + + chan_data_destroy(chan_data); + + return NULL; +} + +void ast_ari_channels_create(struct ast_variable *headers, + struct ast_ari_channels_create_args *args, + struct ast_ari_response *response) +{ + struct ast_assigned_ids assignedids = { + .uniqueid = args->channel_id, + .uniqueid2 = args->other_channel_id, + }; + struct ari_channel_thread_data *chan_data; + struct ast_channel_snapshot *snapshot; + pthread_t thread; + char *dialtech; + char dialdevice[AST_CHANNEL_NAME]; + char *stuff; + int cause; + struct ast_format_cap *request_cap; + struct ast_channel *originator; + + chan_data = ast_calloc(1, sizeof(*chan_data)); + if (!chan_data) { + ast_ari_response_alloc_failed(response); + return; + } + + chan_data->stasis_stuff = ast_str_create(32); + if (!chan_data->stasis_stuff) { + ast_ari_response_alloc_failed(response); + chan_data_destroy(chan_data); + return; + } + + ast_str_append(&chan_data->stasis_stuff, 0, "%s", args->app); + if (!ast_strlen_zero(args->app_args)) { + ast_str_append(&chan_data->stasis_stuff, 0, ",%s", args->app_args); + } + + dialtech = ast_strdupa(args->endpoint); + if ((stuff = strchr(dialtech, '/'))) { + *stuff++ = '\0'; + ast_copy_string(dialdevice, stuff, sizeof(dialdevice)); + } + + originator = ast_channel_get_by_name(args->originator); + if (originator) { + request_cap = ao2_bump(ast_channel_nativeformats(originator)); + } else { + request_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + ast_format_cap_append_by_type(request_cap, AST_MEDIA_TYPE_AUDIO); + } + + chan_data->chan = ast_request(dialtech, request_cap, &assignedids, originator, dialdevice, &cause); + ao2_cleanup(request_cap); + ast_channel_cleanup(originator); + if (!chan_data->chan) { + ast_ari_response_alloc_failed(response); + chan_data_destroy(chan_data); + return; + } + + snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan_data->chan)); + + if (ast_pthread_create_detached(&thread, NULL, ari_channel_thread, chan_data)) { + ast_ari_response_alloc_failed(response); + chan_data_destroy(chan_data); + } else { + ast_ari_response_ok(response, ast_channel_snapshot_to_json(snapshot, NULL)); + } + + ao2_ref(snapshot, -1); +} diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h index 4d3ad5f8b..bd34e0673 100644 --- a/res/ari/resource_channels.h +++ b/res/ari/resource_channels.h @@ -100,6 +100,40 @@ int ast_ari_channels_originate_parse_body( * \param[out] response HTTP response */ void ast_ari_channels_originate(struct ast_variable *headers, struct ast_ari_channels_originate_args *args, struct ast_ari_response *response); +/*! Argument struct for ast_ari_channels_create() */ +struct ast_ari_channels_create_args { + /*! Endpoint for channel communication */ + const char *endpoint; + /*! Stasis Application to place channel into */ + const char *app; + /*! The application arguments to pass to the Stasis application provided by 'app'. Mutually exclusive with 'context', 'extension', 'priority', and 'label'. */ + const char *app_args; + /*! The unique id to assign the channel on creation. */ + const char *channel_id; + /*! The unique id to assign the second channel when using local channels. */ + const char *other_channel_id; + /*! Unique ID of the calling channel */ + const char *originator; +}; +/*! + * \brief Body parsing function for /channels/create. + * \param body The JSON body from which to parse parameters. + * \param[out] args The args structure to parse into. + * \retval zero on success + * \retval non-zero on failure + */ +int ast_ari_channels_create_parse_body( + struct ast_json *body, + struct ast_ari_channels_create_args *args); + +/*! + * \brief Create channel. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void ast_ari_channels_create(struct ast_variable *headers, struct ast_ari_channels_create_args *args, struct ast_ari_response *response); /*! Argument struct for ast_ari_channels_get() */ struct ast_ari_channels_get_args { /*! Channel's id */ diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c index 05d8d0714..dbdd8f34f 100644 --- a/res/res_ari_channels.c +++ b/res/res_ari_channels.c @@ -265,6 +265,131 @@ static void ast_ari_channels_originate_cb( } #endif /* AST_DEVMODE */ +fin: __attribute__((unused)) + return; +} +int ast_ari_channels_create_parse_body( + struct ast_json *body, + struct ast_ari_channels_create_args *args) +{ + struct ast_json *field; + /* Parse query parameters out of it */ + field = ast_json_object_get(body, "endpoint"); + if (field) { + args->endpoint = ast_json_string_get(field); + } + field = ast_json_object_get(body, "app"); + if (field) { + args->app = ast_json_string_get(field); + } + field = ast_json_object_get(body, "appArgs"); + if (field) { + args->app_args = ast_json_string_get(field); + } + field = ast_json_object_get(body, "channelId"); + if (field) { + args->channel_id = ast_json_string_get(field); + } + field = ast_json_object_get(body, "otherChannelId"); + if (field) { + args->other_channel_id = ast_json_string_get(field); + } + field = ast_json_object_get(body, "originator"); + if (field) { + args->originator = ast_json_string_get(field); + } + return 0; +} + +/*! + * \brief Parameter parsing callback for /channels/create. + * \param get_params GET parameters in the HTTP request. + * \param path_vars Path variables extracted from the request. + * \param headers HTTP headers. + * \param[out] response Response to the HTTP request. + */ +static void ast_ari_channels_create_cb( + struct ast_tcptls_session_instance *ser, + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct ast_ari_response *response) +{ + struct ast_ari_channels_create_args args = {}; + struct ast_variable *i; + RAII_VAR(struct ast_json *, body, NULL, ast_json_unref); +#if defined(AST_DEVMODE) + int is_valid; + int code; +#endif /* AST_DEVMODE */ + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "endpoint") == 0) { + args.endpoint = (i->value); + } else + if (strcmp(i->name, "app") == 0) { + args.app = (i->value); + } else + if (strcmp(i->name, "appArgs") == 0) { + args.app_args = (i->value); + } else + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + if (strcmp(i->name, "otherChannelId") == 0) { + args.other_channel_id = (i->value); + } else + if (strcmp(i->name, "originator") == 0) { + args.originator = (i->value); + } else + {} + } + /* Look for a JSON request entity */ + body = ast_http_get_json(ser, headers); + if (!body) { + switch (errno) { + case EFBIG: + ast_ari_response_error(response, 413, "Request Entity Too Large", "Request body too large"); + goto fin; + case ENOMEM: + ast_ari_response_error(response, 500, "Internal Server Error", "Error processing request"); + goto fin; + case EIO: + ast_ari_response_error(response, 400, "Bad Request", "Error parsing request body"); + goto fin; + } + } + if (ast_ari_channels_create_parse_body(body, &args)) { + ast_ari_response_alloc_failed(response); + goto fin; + } + ast_ari_channels_create(headers, &args, response); +#if defined(AST_DEVMODE) + code = response->response_code; + + switch (code) { + case 0: /* Implementation is still a stub, or the code wasn't set */ + is_valid = response->message == NULL; + break; + case 500: /* Internal Server Error */ + case 501: /* Not Implemented */ + is_valid = 1; + break; + default: + if (200 <= code && code <= 299) { + is_valid = ast_ari_validate_channel( + response->message); + } else { + ast_log(LOG_ERROR, "Invalid error response %d for /channels/create\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /channels/create\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + fin: __attribute__((unused)) return; } @@ -2553,6 +2678,15 @@ fin: __attribute__((unused)) return; } +/*! \brief REST handler for /api-docs/channels.{format} */ +static struct stasis_rest_handlers channels_create = { + .path_segment = "create", + .callbacks = { + [AST_HTTP_POST] = ast_ari_channels_create_cb, + }, + .num_children = 0, + .children = { } +}; /*! \brief REST handler for /api-docs/channels.{format} */ static struct stasis_rest_handlers channels_channelId_continue = { .path_segment = "continue", @@ -2715,8 +2849,8 @@ static struct stasis_rest_handlers channels = { [AST_HTTP_GET] = ast_ari_channels_list_cb, [AST_HTTP_POST] = ast_ari_channels_originate_cb, }, - .num_children = 1, - .children = { &channels_channelId, } + .num_children = 2, + .children = { &channels_create,&channels_channelId, } }; static int load_module(void) diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json index bc0879b57..a4489fb7d 100644 --- a/rest-api/api-docs/channels.json +++ b/rest-api/api-docs/channels.json @@ -139,6 +139,68 @@ } ] }, + { + "path": "/channels/create", + "description": "Create a channel and place it in a Stasis app, but do not dial the channel yet.", + "operations": [ + { + "httpMethod": "POST", + "summary": "Create channel.", + "nickname": "create", + "responseClass": "Channel", + "parameters": [ + { + "name": "endpoint", + "description": "Endpoint for channel communication", + "paramType": "query", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "app", + "description": "Stasis Application to place channel into", + "paramType": "query", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "appArgs", + "description": "The application arguments to pass to the Stasis application provided by 'app'. Mutually exclusive with 'context', 'extension', 'priority', and 'label'.", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "channelId", + "description": "The unique id to assign the channel on creation.", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "otherChannelId", + "description": "The unique id to assign the second channel when using local channels.", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "originator", + "description": "Unique ID of the calling channel", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" + } + ] + } + ] + }, { "path": "/channels/{channelId}", "description": "Active channel", -- cgit v1.2.3