[Asterisk-code-review] res stasis: Auto-create context and add ability to switch ap... (asterisk[13])

Benjamin Keith Ford asteriskteam at digium.com
Wed Jan 16 10:32:28 CST 2019


Benjamin Keith Ford has uploaded this change for review. ( https://gerrit.asterisk.org/10882


Change subject: res_stasis: Auto-create context and add ability to switch applications.
......................................................................

res_stasis: Auto-create context and add ability to switch applications.

At AstriCon, there was a strong desire for the ability to completely
bypass dialplan when using ARI. This is possible through the automatic
creation of a context and a couple of extensions whenever an application
is started.

For example, if you have an application named 'ari-example', a context
named 'stasis:ari-example' will be automatically created whenever this
application is started. Two extensions (a match-all extension for Stasis
and a 'h' extension) are created within this context. Any endpoint that
registers to Asterisk within this context will send all calls to the
corresponding Stasis application. When the application is destroyed, the
context is removed.

Another new feature is the ability to move between Stasis applications
within Stasis itself. This can be done by calling 'move' in an
application, providing (at minimum) the channel's id and the application
to switch to. If the application is not registered or active, nothing
will happen and the channel will remain in the current application.
Optionally, a list of arguments can be passed to the function call for
the receiving application. A full example of a 'move' call would look
like this:

client.channels.move(channelId, app, appArgs)

The control object used to control the channel in Stasis can now switch
which application it belongs to, rather than belonging to one Stasis
application for its lifetime. This allows us to use the same control
object instead of having to tear down the current one and create
another.

ASTERISK-28104 #close

Change-Id: Ib6c569468472dbb08905b356887373c81e03015d
---
M CHANGES
M include/asterisk/stasis_app.h
M res/ari/resource_channels.c
M res/ari/resource_channels.h
M res/res_ari_channels.c
M res/res_stasis.c
M res/stasis/app.c
M res/stasis/control.c
M res/stasis/control.h
M rest-api/api-docs/channels.json
10 files changed, 504 insertions(+), 9 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/82/10882/1

diff --git a/CHANGES b/CHANGES
index da58f29..c4fa372 100644
--- a/CHANGES
+++ b/CHANGES
@@ -12,6 +12,26 @@
 --- Functionality changes from Asterisk 13.24.0 to Asterisk 13.25.0 ----------
 ------------------------------------------------------------------------------
 
+ARI
+------------------
+ * Whenever an ARI application is started, a context will be created for it
+   automatically, following the format 'stasis:<app_name>'. Two extensions
+   are also added to this context: a match-all extension, and the 'h'
+   extension. Any phone that registers under this context will place all calls
+   to the corresponding Stasis application.
+
+   To go along with this, a new REST API call has been added: 'move'. It
+   follows the format 'channels/{channelId}/move' and can be used to move
+   channels from one application to another without needing to exit back into
+   the dialplan. An application must be specified, but the passing a list of
+   arguments to the new application is optional. An example call would look
+   like this:
+
+   client.channels.move(channelId=chan.id, app='ari-example', appArgs='a,b,c')
+
+   If the channel was inside of a bridge when switching applications, it will
+   remain there.
+
 res_pjsip
 ------------------
  * Added "send_contact_status_on_update_registration" global configuration option
diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h
index 90a7ea3..e887d14 100644
--- a/include/asterisk/stasis_app.h
+++ b/include/asterisk/stasis_app.h
@@ -520,6 +520,20 @@
 int stasis_app_control_continue(struct stasis_app_control *control, const char *context, const char *extension, int priority);
 
 /*!
+ * \brief Exit \c res_stasis and move to another Stasis application.
+ *
+ * If the channel is no longer in \c res_stasis, this function does nothing.
+ *
+ * \param control Control for \c res_stasis
+ * \param app_name The name of the application to switch to
+ * \param app_args The list of arguments to pass to the application
+ *
+ * \return 0 for success
+ * \return -1 for error
+ */
+int stasis_app_control_move(struct stasis_app_control *control, const char *app_name, const char *app_args);
+
+/*!
  * \brief Redirect a channel in \c res_stasis to a particular endpoint
  *
  * \param control Control for \c res_stasis
diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c
index 04db704..f5065f5 100644
--- a/res/ari/resource_channels.c
+++ b/res/ari/resource_channels.c
@@ -159,6 +159,26 @@
 	ast_ari_response_no_content(response);
 }
 
+void ast_ari_channels_move(struct ast_variable *headers,
+	struct ast_ari_channels_move_args *args,
+	struct ast_ari_response *response)
+{
+	RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+
+	control = find_control(response, args->channel_id);
+	if (!control) {
+		return;
+	}
+
+	if (stasis_app_control_move(control, args->app, args->app_args)) {
+		ast_ari_response_error(response, 500, "Internal Server Error",
+			"Failed to switch Stasis applications");
+		return;
+	}
+
+	ast_ari_response_no_content(response);
+}
+
 void ast_ari_channels_redirect(struct ast_variable *headers,
 	struct ast_ari_channels_redirect_args *args,
 	struct ast_ari_response *response)
diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h
index dd20415..b9e5d02 100644
--- a/res/ari/resource_channels.h
+++ b/res/ari/resource_channels.h
@@ -225,6 +225,34 @@
  * \param[out] response HTTP response
  */
 void ast_ari_channels_continue_in_dialplan(struct ast_variable *headers, struct ast_ari_channels_continue_in_dialplan_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_channels_move() */
+struct ast_ari_channels_move_args {
+	/*! Channel's id */
+	const char *channel_id;
+	/*! The channel will be passed to this Stasis application. */
+	const char *app;
+	/*! The application arguments to pass to the Stasis application provided by 'app'. */
+	const char *app_args;
+};
+/*!
+ * \brief Body parsing function for /channels/{channelId}/move.
+ * \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_move_parse_body(
+	struct ast_json *body,
+	struct ast_ari_channels_move_args *args);
+
+/*!
+ * \brief Move the channel from one Stasis application to another.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_channels_move(struct ast_variable *headers, struct ast_ari_channels_move_args *args, struct ast_ari_response *response);
 /*! Argument struct for ast_ari_channels_redirect() */
 struct ast_ari_channels_redirect_args {
 	/*! Channel's id */
diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c
index dafca15..4eecd45 100644
--- a/res/res_ari_channels.c
+++ b/res/res_ari_channels.c
@@ -658,6 +658,96 @@
 fin: __attribute__((unused))
 	return;
 }
+int ast_ari_channels_move_parse_body(
+	struct ast_json *body,
+	struct ast_ari_channels_move_args *args)
+{
+	struct ast_json *field;
+	/* Parse query parameters out of it */
+	field = ast_json_object_get(body, "channelId");
+	if (field) {
+		args->channel_id = 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);
+	}
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/move.
+ * \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_move_cb(
+	struct ast_tcptls_session_instance *ser,
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
+{
+	struct ast_ari_channels_move_args args = {};
+	struct ast_variable *i;
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
+	for (i = get_params; i; i = i->next) {
+		if (strcmp(i->name, "channelId") == 0) {
+			args.channel_id = (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 (ast_ari_channels_move_parse_body(body, &args)) {
+		ast_ari_response_alloc_failed(response);
+		goto fin;
+	}
+	ast_ari_channels_move(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 not found */
+	case 409: /* Channel not in a Stasis application */
+		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}/move\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/move\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+	return;
+}
 int ast_ari_channels_redirect_parse_body(
 	struct ast_json *body,
 	struct ast_ari_channels_redirect_args *args)
@@ -2313,6 +2403,15 @@
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/channels.json */
+static struct stasis_rest_handlers channels_channelId_move = {
+	.path_segment = "move",
+	.callbacks = {
+		[AST_HTTP_POST] = ast_ari_channels_move_cb,
+	},
+	.num_children = 0,
+	.children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.json */
 static struct stasis_rest_handlers channels_channelId_redirect = {
 	.path_segment = "redirect",
 	.callbacks = {
@@ -2455,8 +2554,8 @@
 		[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_move,&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, }
 };
 /*! \brief REST handler for /api-docs/channels.json */
 static struct stasis_rest_handlers channels = {
diff --git a/res/res_stasis.c b/res/res_stasis.c
index 127d0e8..8e085d8 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -1330,15 +1330,76 @@
 			break;
 		}
 
+		if (control_next_app(control)) {
+			RAII_VAR(struct stasis_app *, last_app, NULL, ao2_cleanup);
+
+			last_app = ao2_find(apps_registry, stasis_app_name(control_app(control)), OBJ_SEARCH_KEY);
+			app = ao2_find(apps_registry, control_next_app(control), OBJ_SEARCH_KEY);
+
+			if (app && app_is_active(app)) {
+
+				if (!stasis_app_channel_is_stasis_end_published(chan)) {
+					res = has_masquerade_store(chan) && app_send_end_msg(last_app, chan);
+					if (res != 0) {
+						ast_log(LOG_ERROR,
+							"Error sending end message to %s\n", stasis_app_name(last_app));
+						ao2_ref(last_app, -1);
+						control_mark_done(control);
+						break;
+					}
+				} else {
+					remove_stasis_end_published(chan);
+				}
+
+				/* We bumped this when trying to find it, so we need to decrement */
+				ao2_ref(last_app, -1);
+
+				cleanup();
+
+				ao2_cleanup(last_app);
+
+				/* This calls ao2_bump! */
+				control_set_app(control, app);
+
+				/* We need to add another masquerade store, otherwise the leave message will
+				 * not show up for the correct application.
+				 */
+				if (add_masquerade_store(chan)) {
+					ast_log(LOG_ERROR, "Failed to attach masquerade detector\n");
+					res = -1;
+					control_mark_done(control);
+					break;
+				}
+
+				res = send_start_msg(control_app(control), chan, control_argc(control), control_argv(control));
+				if (res != 0) {
+					ast_log(LOG_ERROR,
+						"Error sending start message to '%s'\n", stasis_app_name(control_app(control)));
+					remove_masquerade_store(chan);
+					control_mark_done(control);
+					break;
+				}
+
+				/* Done switching applications, free memory and clean up */
+				control_move_cleanup(control);
+			} else {
+				/* If we can't switch applications, do nothing */
+				ast_log(LOG_ERROR, "Could not move to Stasis app '%s' - not registered or not active\n",
+					control_next_app(control));
+			}
+
+			control_move_cleanup(control);
+		}
+
 		last_bridge = bridge;
 		bridge = ao2_bump(stasis_app_get_bridge(control));
 
 		if (bridge != last_bridge) {
 			if (last_bridge) {
-				app_unsubscribe_bridge(app, last_bridge);
+				app_unsubscribe_bridge(control_app(control), last_bridge);
 			}
 			if (bridge) {
-				app_subscribe_bridge(app, bridge);
+				app_subscribe_bridge(control_app(control), bridge);
 			}
 		}
 
@@ -1398,18 +1459,23 @@
 	}
 
 	if (stasis_app_get_bridge(control)) {
-		app_unsubscribe_bridge(app, stasis_app_get_bridge(control));
+		app_unsubscribe_bridge(control_app(control), stasis_app_get_bridge(control));
 	}
 	ao2_cleanup(bridge);
 
+	/* If something went wrong while moving applications, res will not be zero - return here */
+	if (res != 0) {
+		return res;
+	}
+
 	/* Only publish a stasis_end event if it hasn't already been published */
 	if (!stasis_app_channel_is_stasis_end_published(chan)) {
 		/* A masquerade has occurred and this message will be wrong so it
 		 * has already been sent elsewhere. */
-		res = has_masquerade_store(chan) && app_send_end_msg(app, chan);
+		res = has_masquerade_store(chan) && app_send_end_msg(control_app(control), chan);
 		if (res != 0) {
 			ast_log(LOG_ERROR,
-				"Error sending end message to %s\n", app_name);
+				"Error sending end message to %s\n", stasis_app_name(control_app(control)));
 			return res;
 		}
 	} else {
@@ -1429,8 +1495,9 @@
 	/* The control needs to be removed from the controls container in
 	 * case a new PBX is started and ends up coming back into Stasis.
 	 */
-	ao2_cleanup(app);
+	ao2_cleanup(control_app(control));
 	app = NULL;
+
 	control_unlink(control);
 	control = NULL;
 
diff --git a/res/stasis/app.c b/res/stasis/app.c
index ccb93bc..c765a7a 100644
--- a/res/stasis/app.c
+++ b/res/stasis/app.c
@@ -299,6 +299,8 @@
 static void app_dtor(void *obj)
 {
 	struct stasis_app *app = obj;
+	size_t size = strlen("stasis:") + strlen(app->name) + 1;
+	char context_name[size];
 
 	ast_verb(1, "Destroying Stasis app %s\n", app->name);
 
@@ -306,6 +308,10 @@
 	ast_assert(app->bridge_router == NULL);
 	ast_assert(app->endpoint_router == NULL);
 
+	/* Remove the context we created for this application */
+	strcat(strcpy(context_name, "stasis:"), app->name);
+	ast_context_destroy_by_name(context_name, "res_stasis");
+
 	ao2_cleanup(app->topic);
 	app->topic = NULL;
 	ao2_cleanup(app->forwards);
@@ -934,6 +940,8 @@
 	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);
@@ -1009,6 +1017,15 @@
 	app->handler = handler;
 	app->data = ao2_bump(data);
 
+	/* Create a context, a match-all extension, and a 'h' extension for this application */
+	strcat(strcpy(context_name, "stasis:"), name);
+	if (!ast_context_find_or_create(NULL, NULL, context_name, "res_stasis")) {
+		ast_log(LOG_WARNING, "Could not find or create context '%s'\n", context_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");
+	}
+
 	ao2_ref(app, +1);
 	return app;
 }
diff --git a/res/stasis/control.c b/res/stasis/control.c
index 1821f20..c71603a 100644
--- a/res/stasis/control.c
+++ b/res/stasis/control.c
@@ -74,10 +74,24 @@
 	 */
 	struct ast_silence_generator *silgen;
 	/*!
-	 * The app for which this control was created
+	 * The app for which this control is currently controlling.
+	 * This can change through the use of the /channels/{channelId}/move
+	 * command.
 	 */
 	struct stasis_app *app;
 	/*!
+	 * The name of the next Stasis application to move to.
+	 */
+	char *next_app;
+	/*!
+	 * The list of arguments to pass to StasisStart when moving to another app.
+	 */
+	char **argv;
+	/*!
+	 * The number of arguments argv contains.
+	 */
+	int argc;
+	/*!
 	 * When set, /c app_stasis should exit and continue in the dialplan.
 	 */
 	int is_done:1;
@@ -92,6 +106,8 @@
 	ast_channel_cleanup(control->channel);
 	ao2_cleanup(control->app);
 
+	control_move_cleanup(control);
+
 	ast_cond_destroy(&control->wait_cond);
 	AST_LIST_HEAD_DESTROY(&control->add_rules);
 	AST_LIST_HEAD_DESTROY(&control->remove_rules);
@@ -130,6 +146,10 @@
 		return NULL;
 	}
 
+	control->next_app = NULL;
+	control->argv = NULL;
+	control->argc = 0;
+
 	return control;
 }
 
@@ -463,6 +483,103 @@
 	return 0;
 }
 
+struct stasis_app_control_move_data {
+	char *app_name;
+	char **argv;
+	int argc;
+};
+
+static int app_control_move(struct stasis_app_control *control,
+	struct ast_channel *chan, void *data)
+{
+	struct stasis_app_control_move_data *move_data = data;
+	size_t size;
+	int idx;
+
+	size = strlen(move_data->app_name) * sizeof(*move_data->app_name) + 1;
+	if (!(control->next_app = ast_calloc(1, size))) {
+		ast_log(LOG_ERROR, "Allocation failed for next app\n");
+		return -1;
+	}
+
+	ast_copy_string(control->next_app, S_OR(move_data->app_name, ""), size);
+	control->argc = move_data->argc;
+
+	if (move_data->argv) {
+		size = sizeof(*move_data->argv) * control->argc;
+		if (!(control->argv = ast_calloc(1, size))) {
+			ast_log(LOG_ERROR, "Allocation failed for argument list\n");
+			control_move_cleanup(control);
+			return -1;
+		}
+
+		for (idx = 0; idx < control->argc; idx++) {
+			size = strlen(move_data->argv[idx]) * sizeof(*move_data->argv[idx]) + 1;
+			if (!(control->argv[idx] = ast_calloc(1, size))) {
+				ast_log(LOG_ERROR, "Allocation failed for argument\n");
+				control_move_cleanup(control);
+				return -1;
+			}
+			ast_copy_string(control->argv[idx], S_OR(move_data->argv[idx], ""), size);
+		}
+	} else {
+		control->argv = NULL;
+	}
+
+	return 0;
+}
+
+int stasis_app_control_move(struct stasis_app_control *control, const char *app_name, const char *app_args)
+{
+	struct stasis_app_control_move_data *move_data;
+	size_t size;
+	int argc = 0;
+	char *iter;
+	char *token;
+
+	if (!(move_data = ast_calloc(1, sizeof(*move_data)))) {
+		return -1;
+	}
+
+	size = strlen(app_name) * sizeof(*app_name) + 1;
+	if (!(move_data->app_name = ast_calloc(1, size))) {
+		return -1;
+	}
+
+	ast_copy_string(move_data->app_name, S_OR(app_name, ""), size);
+
+	if (app_args) {
+		size = strlen(app_args) * sizeof(*app_args) + 1;
+		if (!(iter = ast_calloc(1, size))) {
+			return -1;
+		}
+
+		ast_copy_string(iter, S_OR(app_args, ""), size);
+
+		if (!(move_data->argv = ast_calloc(1, sizeof(*move_data->argv)))) {
+			return -1;
+		}
+
+		while ((token = strtok_r(iter, ",", &iter))) {
+			if (!(move_data->argv = ast_realloc(move_data->argv, sizeof(*move_data->argv) * (argc + 1)))) {
+				return -1;
+			}
+			size = strlen(token) * sizeof(*token) + 1;
+			move_data->argv[argc] = ast_calloc(1, size);
+			ast_copy_string(move_data->argv[argc], S_OR(token, ""), size);
+			argc++;
+		}
+	} else {
+		move_data->argv = NULL;
+	}
+
+	move_data->argc = argc;
+
+	stasis_app_send_command_async(control, app_control_move, move_data, ast_free_ptr);
+
+	return 0;
+}
+
 static int app_control_redirect(struct stasis_app_control *control,
 	struct ast_channel *chan, void *data)
 {
@@ -1278,3 +1395,46 @@
 {
 	return control->app;
 }
+
+void control_set_app(struct stasis_app_control *control, struct stasis_app *app)
+{
+	control->app = ao2_bump(app);
+}
+
+char *control_next_app(struct stasis_app_control *control)
+{
+	return control->next_app;
+}
+
+void control_move_cleanup(struct stasis_app_control *control)
+{
+	if (control->next_app) {
+		ast_free(control->next_app);
+	}
+	control->next_app = NULL;
+
+	if (control->argv) {
+		int idx;
+
+		for (idx = 0; idx < control->argc; idx++) {
+			if (control->argv[idx]) {
+				ast_free(control->argv[idx]);
+			}
+		}
+
+		ast_free(control->argv);
+	}
+	control->argv = NULL;
+
+	control->argc = 0;
+}
+
+char **control_argv(struct stasis_app_control *control)
+{
+	return control->argv;
+}
+
+int control_argc(struct stasis_app_control *control)
+{
+	return control->argc;
+}
diff --git a/res/stasis/control.h b/res/stasis/control.h
index 868a809..d933d52 100644
--- a/res/stasis/control.h
+++ b/res/stasis/control.h
@@ -108,6 +108,28 @@
 struct stasis_app *control_app(struct stasis_app_control *control);
 
 /*!
+ * \brief Set the application the control object belongs to
+ *
+ * \param control The control for the channel
+ * \param app The application this control will now belong to
+ */
+void control_set_app(struct stasis_app_control *control, struct stasis_app *app);
+
+char *control_next_app(struct stasis_app_control *control);
+
+/*!
+ * \brief Free any memory that was allocated for switching applications via
+ * /channels/{channelId}/move
+ *
+ * \param control The control for the channel
+ */
+void control_move_cleanup(struct stasis_app_control *control);
+
+char **control_argv(struct stasis_app_control *control);
+
+int control_argc(struct stasis_app_control *control);
+
+/*!
  * \brief Command callback for adding a channel to a bridge
  *
  * \param control The control for chan
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index 487db44..07a20a9 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -420,6 +420,54 @@
 			]
 		},
 		{
+			"path": "/channels/{channelId}/move",
+			"description": "Move the channel from one Stasis application to another.",
+			"operations": [
+				{
+					"httpMethod": "POST",
+					"summary": "Move the channel from one Stasis application to another.",
+					"nickname": "move",
+					"responseClass": "void",
+					"parameters": [
+						{
+							"name": "channelId",
+							"description": "Channel's id",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "app",
+							"description": "The channel will be passed to this Stasis application.",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "appArgs",
+							"description": "The application arguments to pass to the Stasis application provided by 'app'.",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "string"
+						}
+					],
+					"errorResponses": [
+						{
+							"code": "404",
+							"reason": "Channel not found"
+						},
+						{
+							"code": "409",
+							"reason": "Channel not in a Stasis application"
+						}
+					]
+				}
+			]
+		},
+		{
 			"path": "/channels/{channelId}/redirect",
 			"description": "Inform the channel that it should redirect itself to a different location. Note that this will almost certainly cause the channel to exit the application.",
 			"operations": [

-- 
To view, visit https://gerrit.asterisk.org/10882
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: 13
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ib6c569468472dbb08905b356887373c81e03015d
Gerrit-Change-Number: 10882
Gerrit-PatchSet: 1
Gerrit-Owner: Benjamin Keith Ford <bford at digium.com>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20190116/ba922ab8/attachment-0001.html>


More information about the asterisk-code-review mailing list