ARI: Add method to Dial a created channel.
authorMark Michelson <mmichelson@digium.com>
Wed, 30 Mar 2016 22:18:39 +0000 (17:18 -0500)
committerJoshua Colp <jcolp@digium.com>
Tue, 5 Apr 2016 23:14:17 +0000 (18:14 -0500)
This adds a new ARI method that allows for you to dial a channel that
you previously created in ARI.

By combining this with the create method for channels, it allows for a
workflow where a channel can be created, manipulated, and then dialed.
The channel is under control of the ARI application during all stages of
the Dial and can even be manipulated based on channel state changes
observed within an ARI application.

The overarching goal for this is to eventually be able to add a dialed
channel to a Stasis bridge earlier than the "Up" state. However, at the
moment more work is needed in the Dial and Bridge APIs in order to
facilitate that.

ASTERISK-25889 #close

Change-Id: Ic6c399c791e66c4aa52454222fe4f8b02483a205

CHANGES
include/asterisk/stasis_app.h
res/ari/resource_channels.c
res/ari/resource_channels.h
res/res_ari_channels.c
res/res_stasis.c
res/stasis/control.c
rest-api/api-docs/channels.json

diff --git a/CHANGES b/CHANGES
index b652256..c30ed33 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -20,6 +20,9 @@ ARI
  allows for an application writer to create a channel, perform manipulations on it,
  and then delay dialing the channel until later.
 
+ * To complement the "create" method, a "dial" method has been added to the channels
+ resource in order to place a call to a created channel.
+
 Applications
 ------------------
 
index f2b07e0..981d2d6 100644 (file)
@@ -451,23 +451,6 @@ const char *stasis_app_control_get_channel_id(
        const struct stasis_app_control *control);
 
 /*!
- * \brief Dial an endpoint and bridge it to a channel in \c res_stasis
- *
- * If the channel is no longer in \c res_stasis, this function does nothing.
- *
- * \param control Control for \c res_stasis
- * \param endpoint The endpoint to dial.
- * \param exten Extension to dial if no endpoint specified.
- * \param context Context to use with extension.
- * \param timeout The amount of time to wait for answer, before giving up.
- *
- * \return 0 for success
- * \return -1 for error.
- */
-int stasis_app_control_dial(struct stasis_app_control *control, const char *endpoint, const char *exten,
-                            const char *context, int timeout);
-
-/*!
  * \brief Apply a bridge role to a channel controlled by a stasis app control
  *
  * \param control Control for \c res_stasis
@@ -862,6 +845,20 @@ int stasis_app_channel_unreal_set_internal(struct ast_channel *chan);
  */
 int stasis_app_channel_set_internal(struct ast_channel *chan);
 
+struct ast_dial;
+
+/*!
+ * \brief Dial a channel
+ * \param control Control for \c res_stasis.
+ * \param dial The ast_dial for the outbound channel
+ */
+int stasis_app_control_dial(struct stasis_app_control *control, struct ast_dial *dial);
+
+/*!
+ * \brief Get dial structure on a control
+ */
+struct ast_dial *stasis_app_get_dial(struct stasis_app_control *control);
+
 /*! @} */
 
 #endif /* _ASTERISK_STASIS_APP_H */
index 1954d6b..c838bc3 100644 (file)
@@ -1570,3 +1570,71 @@ void ast_ari_channels_create(struct ast_variable *headers,
 
        ao2_ref(snapshot, -1);
 }
+
+void ast_ari_channels_dial(struct ast_variable *headers,
+       struct ast_ari_channels_dial_args *args,
+       struct ast_ari_response *response)
+{
+       RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_channel *, caller, NULL, ast_channel_cleanup);
+       struct ast_channel *callee;
+       struct ast_dial *dial;
+
+       control = find_control(response, args->channel_id);
+       if (control == NULL) {
+               /* Response filled in by find_control */
+               return;
+       }
+
+       caller = ast_channel_get_by_name(args->caller);
+
+       callee = ast_channel_get_by_name(args->channel_id);
+       if (!callee) {
+               ast_ari_response_error(response, 404, "Not Found",
+                       "Callee not found");
+               return;
+       }
+
+       if (ast_channel_state(callee) != AST_STATE_DOWN) {
+               ast_channel_unref(callee);
+               ast_ari_response_error(response, 409, "Conflict",
+                       "Channel is not in the 'Down' state");
+               return;
+       }
+
+       dial = ast_dial_create();
+       if (!dial) {
+               ast_channel_unref(callee);
+               ast_ari_response_alloc_failed(response);
+               return;
+       }
+
+       if (ast_dial_append_channel(dial, callee) < 0) {
+               ast_channel_unref(callee);
+               ast_dial_destroy(dial);
+               ast_ari_response_alloc_failed(response);
+               return;
+       }
+
+       /* From this point, we don't have to unref the callee channel on
+        * failure paths because the dial owns the reference to the called
+        * channel and will unref the channel for us
+        */
+
+       if (ast_dial_prerun(dial, caller, NULL)) {
+               ast_dial_destroy(dial);
+               ast_ari_response_alloc_failed(response);
+               return;
+       }
+
+       ast_dial_set_user_data(dial, control);
+       ast_dial_set_global_timeout(dial, args->timeout * 1000);
+
+       if (stasis_app_control_dial(control, dial)) {
+               ast_dial_destroy(dial);
+               ast_ari_response_alloc_failed(response);
+               return;
+       }
+
+       ast_ari_response_no_content(response);
+}
index bd34e06..89b466d 100644 (file)
@@ -739,5 +739,33 @@ int ast_ari_channels_snoop_channel_with_id_parse_body(
  * \param[out] response HTTP response
  */
 void ast_ari_channels_snoop_channel_with_id(struct ast_variable *headers, struct ast_ari_channels_snoop_channel_with_id_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_channels_dial() */
+struct ast_ari_channels_dial_args {
+       /*! Channel's id */
+       const char *channel_id;
+       /*! Channel ID of caller */
+       const char *caller;
+       /*! Dial timeout */
+       int timeout;
+};
+/*!
+ * \brief Body parsing function for /channels/{channelId}/dial.
+ * \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_dial_parse_body(
+       struct ast_json *body,
+       struct ast_ari_channels_dial_args *args);
+
+/*!
+ * \brief Dial a created channel.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_channels_dial(struct ast_variable *headers, struct ast_ari_channels_dial_args *args, struct ast_ari_response *response);
 
 #endif /* _ASTERISK_RESOURCE_CHANNELS_H */
index dbdd8f3..1f08181 100644 (file)
@@ -2677,6 +2677,111 @@ static void ast_ari_channels_snoop_channel_with_id_cb(
 fin: __attribute__((unused))
        return;
 }
+int ast_ari_channels_dial_parse_body(
+       struct ast_json *body,
+       struct ast_ari_channels_dial_args *args)
+{
+       struct ast_json *field;
+       /* Parse query parameters out of it */
+       field = ast_json_object_get(body, "caller");
+       if (field) {
+               args->caller = ast_json_string_get(field);
+       }
+       field = ast_json_object_get(body, "timeout");
+       if (field) {
+               args->timeout = ast_json_integer_get(field);
+       }
+       return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/dial.
+ * \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_dial_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_dial_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, "caller") == 0) {
+                       args.caller = (i->value);
+               } else
+               if (strcmp(i->name, "timeout") == 0) {
+                       args.timeout = atoi(i->value);
+               } else
+               {}
+       }
+       for (i = path_vars; i; i = i->next) {
+               if (strcmp(i->name, "channelId") == 0) {
+                       args.channel_id = (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_dial_parse_body(body, &args)) {
+               ast_ari_response_alloc_failed(response);
+               goto fin;
+       }
+       ast_ari_channels_dial(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 */
+       case 404: /* Channel cannot be found. */
+       case 409: /* Channel cannot be dialed. */
+               is_valid = 1;
+               break;
+       default:
+               if (200 <= code && code <= 299) {
+                       is_valid = ast_ari_validate_void(
+                               response->message);
+               } else {
+                       ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/dial\n", code);
+                       is_valid = 0;
+               }
+       }
+
+       if (!is_valid) {
+               ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/dial\n");
+               ast_ari_response_error(response, 500,
+                       "Internal Server Error", "Response validation failed");
+       }
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+       return;
+}
 
 /*! \brief REST handler for /api-docs/channels.{format} */
 static struct stasis_rest_handlers channels_create = {
@@ -2831,6 +2936,15 @@ static struct stasis_rest_handlers channels_channelId_snoop = {
        .children = { &channels_channelId_snoop_snoopId, }
 };
 /*! \brief REST handler for /api-docs/channels.{format} */
+static struct stasis_rest_handlers channels_channelId_dial = {
+       .path_segment = "dial",
+       .callbacks = {
+               [AST_HTTP_POST] = ast_ari_channels_dial_cb,
+       },
+       .num_children = 0,
+       .children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.{format} */
 static struct stasis_rest_handlers channels_channelId = {
        .path_segment = "channelId",
        .is_wildcard = 1,
@@ -2839,8 +2953,8 @@ static struct stasis_rest_handlers channels_channelId = {
                [AST_HTTP_POST] = ast_ari_channels_originate_with_id_cb,
                [AST_HTTP_DELETE] = ast_ari_channels_hangup_cb,
        },
-       .num_children = 13,
-       .children = { &channels_channelId_continue,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop, }
+       .num_children = 14,
+       .children = { &channels_channelId_continue,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop,&channels_channelId_dial, }
 };
 /*! \brief REST handler for /api-docs/channels.{format} */
 static struct stasis_rest_handlers channels = {
index 63c565d..02645f7 100644 (file)
@@ -1282,6 +1282,7 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
                int r;
                int command_count;
                RAII_VAR(struct ast_bridge *, last_bridge, NULL, ao2_cleanup);
+               struct ast_dial *dial;
 
                /* Check to see if a bridge absorbed our hangup frame */
                if (ast_check_hangup_locked(chan)) {
@@ -1291,6 +1292,7 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
 
                last_bridge = bridge;
                bridge = ao2_bump(stasis_app_get_bridge(control));
+               dial = stasis_app_get_dial(control);
 
                if (bridge != last_bridge) {
                        app_unsubscribe_bridge(app, last_bridge);
@@ -1299,8 +1301,8 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
                        }
                }
 
-               if (bridge) {
-                       /* Bridge is handling channel frames */
+               if (bridge || dial) {
+                       /* Bridge/dial is handling channel frames */
                        control_wait(control);
                        control_dispatch_all(control, chan);
                        continue;
index 41d538c..ecd1faf 100644 (file)
@@ -78,6 +78,10 @@ struct stasis_app_control {
         */
        struct stasis_app *app;
        /*!
+        * If channel is being dialed, the dial structure.
+        */
+       struct ast_dial *dial;
+       /*!
         * When set, /c app_stasis should exit and continue in the dialplan.
         */
        int is_done:1;
@@ -272,89 +276,6 @@ static struct stasis_app_command *exec_command(
        return exec_command_on_condition(control, command_fn, data, data_destructor, NULL);
 }
 
-struct stasis_app_control_dial_data {
-       char endpoint[AST_CHANNEL_NAME];
-       int timeout;
-};
-
-static int app_control_dial(struct stasis_app_control *control,
-       struct ast_channel *chan, void *data)
-{
-       RAII_VAR(struct ast_dial *, dial, ast_dial_create(), ast_dial_destroy);
-       struct stasis_app_control_dial_data *dial_data = data;
-       enum ast_dial_result res;
-       char *tech, *resource;
-       struct ast_channel *new_chan;
-       RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
-
-       tech = dial_data->endpoint;
-       if (!(resource = strchr(tech, '/'))) {
-               return -1;
-       }
-       *resource++ = '\0';
-
-       if (!dial) {
-               ast_log(LOG_ERROR, "Failed to create dialing structure.\n");
-               return -1;
-       }
-
-       if (ast_dial_append(dial, tech, resource, NULL) < 0) {
-               ast_log(LOG_ERROR, "Failed to add %s/%s to dialing structure.\n", tech, resource);
-               return -1;
-       }
-
-       ast_dial_set_global_timeout(dial, dial_data->timeout);
-
-       res = ast_dial_run(dial, NULL, 0);
-       if (res != AST_DIAL_RESULT_ANSWERED || !(new_chan = ast_dial_answered_steal(dial))) {
-               return -1;
-       }
-
-       if (!(bridge = ast_bridge_basic_new())) {
-               ast_log(LOG_ERROR, "Failed to create basic bridge.\n");
-               return -1;
-       }
-
-       if (ast_bridge_impart(bridge, new_chan, NULL, NULL,
-               AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
-               ast_hangup(new_chan);
-       } else {
-               control_add_channel_to_bridge(control, chan, bridge);
-       }
-
-       return 0;
-}
-
-int stasis_app_control_dial(struct stasis_app_control *control, const char *endpoint, const char *exten, const char *context,
-                           int timeout)
-{
-       struct stasis_app_control_dial_data *dial_data;
-
-       if (!(dial_data = ast_calloc(1, sizeof(*dial_data)))) {
-               return -1;
-       }
-
-       if (!ast_strlen_zero(endpoint)) {
-               ast_copy_string(dial_data->endpoint, endpoint, sizeof(dial_data->endpoint));
-       } else if (!ast_strlen_zero(exten) && !ast_strlen_zero(context)) {
-               snprintf(dial_data->endpoint, sizeof(dial_data->endpoint), "Local/%s@%s", exten, context);
-       } else {
-               return -1;
-       }
-
-       if (timeout > 0) {
-               dial_data->timeout = timeout * 1000;
-       } else if (timeout == -1) {
-               dial_data->timeout = -1;
-       } else {
-               dial_data->timeout = 30000;
-       }
-
-       stasis_app_send_command_async(control, app_control_dial, dial_data, ast_free_ptr);
-
-       return 0;
-}
-
 static int app_control_add_role(struct stasis_app_control *control,
                struct ast_channel *chan, void *data)
 {
@@ -1185,3 +1106,84 @@ struct stasis_app *control_app(struct stasis_app_control *control)
 {
        return control->app;
 }
+
+static void app_control_dial_destroy(void *data)
+{
+       struct ast_dial *dial = data;
+
+       ast_dial_join(dial);
+       ast_dial_destroy(dial);
+}
+
+static int app_control_remove_dial(struct stasis_app_control *control,
+       struct ast_channel *chan, void *data)
+{
+       if (ast_dial_state(control->dial) != AST_DIAL_RESULT_ANSWERED) {
+               ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT);
+       }
+       control->dial = NULL;
+       return 0;
+}
+
+static void on_dial_state(struct ast_dial *dial)
+{
+       enum ast_dial_result state;
+       struct stasis_app_control *control;
+       struct ast_channel *chan;
+
+       state = ast_dial_state(dial);
+       control = ast_dial_get_user_data(dial);
+
+       switch (state) {
+       case AST_DIAL_RESULT_ANSWERED:
+               /* Need to steal the reference to the answered channel so that dial doesn't
+                * try to hang it up when we destroy the dial structure.
+                */
+               chan = ast_dial_answered_steal(dial);
+               ast_channel_unref(chan);
+               /* Fall through intentionally */
+       case AST_DIAL_RESULT_INVALID:
+       case AST_DIAL_RESULT_FAILED:
+       case AST_DIAL_RESULT_TIMEOUT:
+       case AST_DIAL_RESULT_HANGUP:
+       case AST_DIAL_RESULT_UNANSWERED:
+               /* The dial has completed, so we need to break the Stasis loop so
+                * that the channel's frames are handled in the proper place now.
+                */
+               stasis_app_send_command_async(control, app_control_remove_dial, dial, app_control_dial_destroy);
+               break;
+       case AST_DIAL_RESULT_TRYING:
+       case AST_DIAL_RESULT_RINGING:
+       case AST_DIAL_RESULT_PROGRESS:
+       case AST_DIAL_RESULT_PROCEEDING:
+               break;
+       }
+}
+
+static int app_control_dial(struct stasis_app_control *control,
+       struct ast_channel *chan, void *data)
+{
+       struct ast_dial *dial = data;
+
+       ast_dial_set_state_callback(dial, on_dial_state);
+       /* The dial API gives the option of providing a caller channel, but for
+        * Stasis, we really don't want to do that. The Dial API will take liberties such
+        * as passing frames along to the calling channel (think ringing, progress, etc.).
+        * This is not desirable in ARI applications since application writers should have
+        * control over what does/does not get indicated to the calling channel
+        */
+       ast_dial_run(dial, NULL, 1);
+       control->dial = dial;
+
+       return 0;
+}
+
+struct ast_dial *stasis_app_get_dial(struct stasis_app_control *control)
+{
+       return control->dial;
+}
+
+int stasis_app_control_dial(struct stasis_app_control *control, struct ast_dial *dial)
+{
+       return stasis_app_send_command_async(control, app_control_dial, dial, NULL);
+}
index a4489fb..2389f7c 100644 (file)
                                        ]
                                }
                        ]
+               },
+               {
+                       "path": "/channels/{channelId}/dial",
+                       "description": "Dial a channel",
+                       "operations": [
+                               {
+                                       "httpMethod": "POST",
+                                       "summary": "Dial a created channel.",
+                                       "nickname": "dial",
+                                       "responseClass": "void",
+                                       "parameters": [
+                                               {
+                                                       "name": "channelId",
+                                                       "description": "Channel's id",
+                                                       "paramType": "path",
+                                                       "required": true,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "caller",
+                                                       "description": "Channel ID of caller",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "string"
+                                               },
+                                               {
+                                                       "name": "timeout",
+                                                       "description": "Dial timeout",
+                                                       "paramType": "query",
+                                                       "required": false,
+                                                       "allowMultiple": false,
+                                                       "dataType": "int",
+                                                       "defaultValue": 0,
+                                                       "allowableValues": {
+                                                               "valueType": "RANGE",
+                                                               "min": 0
+                                                       }
+                                               }
+                                       ],
+                                       "errorResponses": [
+                                               {
+                                                       "code": 404,
+                                                       "reason": "Channel cannot be found."
+                                               },
+                                               {
+                                                       "code": 409,
+                                                       "reason": "Channel cannot be dialed."
+                                               }
+                                       ]
+                               }
+                       ]
                }
        ],
        "models": {