[Asterisk-code-review] res stasis: Add ability to switch applications. (asterisk[13])

Benjamin Keith Ford asteriskteam at digium.com
Tue Jan 29 11:56:10 CST 2019


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


Change subject: res_stasis: Add ability to switch applications.
......................................................................

res_stasis: Add ability to switch applications.

Added the ability to move between Stasis applications within Stasis.
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-28267 #close

Change-Id: I43d12b10045a98a8d42541889b85695be26f288a
---
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/control.c
M res/stasis/control.h
M rest-api/api-docs/channels.json
9 files changed, 496 insertions(+), 27 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/37/10937/1

diff --git a/CHANGES b/CHANGES
index da58f29..34985bc 100644
--- a/CHANGES
+++ b/CHANGES
@@ -12,6 +12,19 @@
 --- Functionality changes from Asterisk 13.24.0 to Asterisk 13.25.0 ----------
 ------------------------------------------------------------------------------
 
+ARI
+------------------
+ * 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..a69887c 100644
--- a/res/res_ari_channels.c
+++ b/res/res_ari_channels.c
@@ -658,6 +658,95 @@
 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, "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, "app") == 0) {
+			args.app = (i->value);
+		} else
+		if (strcmp(i->name, "appArgs") == 0) {
+			args.app_args = (i->value);
+		} else
+		{}
+	}
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "channelId") == 0) {
+			args.channel_id = (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 +2402,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 +2553,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..759baf7 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -1266,7 +1266,6 @@
 {
 	SCOPED_MODULE_USE(ast_module_info->self);
 
-	RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
 	RAII_VAR(struct stasis_app_control *, control, NULL, control_unlink);
 	struct ast_bridge *bridge = NULL;
 	int res = 0;
@@ -1283,21 +1282,19 @@
 		return -1;
 	}
 
-	app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
-	if (!app) {
-		ast_log(LOG_ERROR,
-			"Stasis app '%s' not registered\n", app_name);
-		return -1;
-	}
-	if (!app_is_active(app)) {
-		ast_log(LOG_ERROR,
-			"Stasis app '%s' not active\n", app_name);
+	control = control_create(chan, ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY));
+	if (!control) {
+		ast_log(LOG_ERROR, "Control allocation failed or Stasis app '%s' not registered\n", app_name);
 		return -1;
 	}
 
-	control = control_create(chan, app);
-	if (!control) {
-		ast_log(LOG_ERROR, "Allocated failed\n");
+	if (!control_app(control)) {
+		ast_log(LOG_ERROR, "Stasis app '%s' not registered\n", app_name);
+		return -1;
+	}
+
+	if (!app_is_active(control_app(control))) {
+		ast_log(LOG_ERROR, "Stasis app '%s' not active\n", app_name);
 		return -1;
 	}
 	ao2_link(app_controls, control);
@@ -1307,7 +1304,7 @@
 		return -1;
 	}
 
-	res = send_start_msg(app, chan, argc, argv);
+	res = send_start_msg(control_app(control), chan, argc, argv);
 	if (res != 0) {
 		ast_log(LOG_ERROR,
 			"Error sending start message to '%s'\n", app_name);
@@ -1330,15 +1327,81 @@
 			break;
 		}
 
+		/* control->next_app is only modified within the control thread, so this is safe */
+		if (control_next_app(control)) {
+			RAII_VAR(struct stasis_app *, next_app, NULL, ao2_cleanup);
+
+			next_app = ao2_find(apps_registry, control_next_app(control), OBJ_SEARCH_KEY);
+
+			if (next_app && app_is_active(next_app)) {
+
+				/* If something goes wrong in this conditional, res will need to be non-zero
+				 * so that the code below the exec loop knows something went wrong during a move.
+				 */
+				if (!stasis_app_channel_is_stasis_end_published(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", stasis_app_name(control_app(control)));
+						control_mark_done(control);
+						break;
+					}
+				} else {
+					remove_stasis_end_published(chan);
+				}
+
+				/* This will ao2_bump next_app, and unref the previous app by 1 */
+				control_set_app(control, next_app);
+
+				/* There's a chance that the previous application is ready for clean up, so go ahead
+				 * and do that now.
+				 */
+				cleanup();
+
+				/* 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 */
+				if (next_app) {
+					ast_log(LOG_ERROR, "Could not move to Stasis app '%s' - not registered\n",
+						control_next_app(control));
+				} else {
+					ast_log(LOG_ERROR, "Could not move to Stasis app '%s' - 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 +1461,18 @@
 	}
 
 	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);
 
 	/* Only publish a stasis_end event if it hasn't already been published */
-	if (!stasis_app_channel_is_stasis_end_published(chan)) {
+	if (!res && !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,12 +1492,10 @@
 	/* 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);
-	app = NULL;
 	control_unlink(control);
 	control = NULL;
 
-	if (!ast_channel_pbx(chan)) {
+	if (!res && !ast_channel_pbx(chan)) {
 		int chan_hungup;
 
 		/* The ASYNCGOTO softhangup flag may have broken the channel out of
diff --git a/res/stasis/control.c b/res/stasis/control.c
index 1821f20..73043b6 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);
@@ -118,7 +134,7 @@
 		return NULL;
 	}
 
-	control->app = ao2_bump(app);
+	control->app = app;
 
 	ast_channel_ref(channel);
 	control->channel = channel;
@@ -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,47 @@
 {
 	return control->app;
 }
+
+void control_set_app(struct stasis_app_control *control, struct stasis_app *app)
+{
+	ao2_cleanup(control->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..e37639e 100644
--- a/res/stasis/control.h
+++ b/res/stasis/control.h
@@ -37,6 +37,8 @@
  *
  * \return New control object.
  * \return \c NULL on error.
+ *
+ * \note This function inherits app's ref rather than bumping app!
  */
 struct stasis_app_control *control_create(struct ast_channel *channel, struct stasis_app *app);
 
@@ -108,6 +110,30 @@
 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
+ *
+ * \note This will unref the previous app by 1, and bump app by 1
+ */
+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..afe95dc 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": "path",
+							"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/10937
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: I43d12b10045a98a8d42541889b85695be26f288a
Gerrit-Change-Number: 10937
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/20190129/681d69a9/attachment-0001.html>


More information about the asterisk-code-review mailing list