[Asterisk-code-review] res/stasis: Add CLI commands for displaying/debugging ARI apps (asterisk[13])

Joshua Colp asteriskteam at digium.com
Wed Nov 2 05:23:52 CDT 2016


Joshua Colp has submitted this change and it was merged. ( https://gerrit.asterisk.org/4164 )

Change subject: res/stasis: Add CLI commands for displaying/debugging ARI apps
......................................................................


res/stasis: Add CLI commands for displaying/debugging ARI apps

This patch adds three new CLI commands:
 - ari show apps: list the registered ARI applications
 - ari show app: show detailed information about an ARI application
 - ari set debug: dump events being sent to an ARI application

Note that while these CLI commands live in the res_stasis module, we use
the 'ari' family for these commands. This was done as most users of
Asterisk aren't aware of the semantic differences between ARI and
res_stasis, and some 'ari' CLI commands already exist.

ASTERISK-26488 #close

Change-Id: I51ad6ff0cabee0d69db06858c13f18b1c513c9f5
---
M CHANGES
M include/asterisk/stasis_app.h
M res/ari/ari_websockets.c
M res/res_stasis.c
M res/stasis/app.c
M res/stasis/app.h
A res/stasis/cli.c
A res/stasis/cli.h
8 files changed, 413 insertions(+), 0 deletions(-)

Approvals:
  Mark Michelson: Looks good to me, but someone else must approve
  George Joseph: Looks good to me, approved
  Anonymous Coward #1000019: Verified
  Joshua Colp: Looks good to me, but someone else must approve



diff --git a/CHANGES b/CHANGES
index b0b9885..fbad201 100644
--- a/CHANGES
+++ b/CHANGES
@@ -28,6 +28,18 @@
    to asymmetric using the "asymmetric_rtp_codec" endpoint option. If this
    option is set then the sending and received codec are allowed to differ.
 
+CLI Commands
+------------------
+ * Three new CLI commands have been added for ARI:
+   - ari show apps:
+      Displays a listing of all registered ARI applications.
+   - ari show app <name>:
+      Display detailed information about a registered ARI application.
+   - ari set debug <name> <on|off>:
+      Enable/disable debugging of an ARI application. When debugged, verbose
+      information will be sent to the Asterisk CLI.
+
+
 ------------------------------------------------------------------------------
 --- Functionality changes from Asterisk 13.11.0 to Asterisk 13.12.0 ----------
 ------------------------------------------------------------------------------
diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h
index 8ceeffb..53ce116 100644
--- a/include/asterisk/stasis_app.h
+++ b/include/asterisk/stasis_app.h
@@ -77,6 +77,16 @@
 struct ao2_container *stasis_app_get_all(void);
 
 /*!
+ * \brief Retrieve a handle to a Stasis application by its name
+ *
+ * \param name The name of the registered Stasis application
+ *
+ * \return \c stasis_app on success.
+ * \return \c NULL on error.
+ */
+struct stasis_app *stasis_app_get_by_name(const char *name);
+
+/*!
  * \brief Register a new Stasis application.
  *
  * If an application is already registered with the given name, the old
diff --git a/res/ari/ari_websockets.c b/res/ari/ari_websockets.c
index 8e11629..f06a667 100644
--- a/res/ari/ari_websockets.c
+++ b/res/ari/ari_websockets.c
@@ -174,7 +174,9 @@
 		return -1;
 	}
 
+#ifdef AST_DEVMODE
 	ast_debug(3, "Examining ARI event (length %u): \n%s\n", (unsigned int) strlen(str), str);
+#endif
 	if (ast_websocket_write_string(session->ws_session, str)) {
 		ast_log(LOG_NOTICE, "Problem occurred during websocket write, websocket closed\n");
 		return -1;
diff --git a/res/res_stasis.c b/res/res_stasis.c
index 11aeb43..1183f67 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -67,6 +67,7 @@
 #include "stasis/app.h"
 #include "stasis/control.h"
 #include "stasis/messaging.h"
+#include "stasis/cli.h"
 #include "stasis/stasis_bridge.h"
 #include "asterisk/core_unreal.h"
 #include "asterisk/musiconhold.h"
@@ -1475,6 +1476,11 @@
 	return res;
 }
 
+struct stasis_app *stasis_app_get_by_name(const char *name)
+{
+	return find_app_by_name(name);
+}
+
 static int append_name(void *obj, void *arg, int flags)
 {
 	struct stasis_app *app = obj;
@@ -1947,6 +1953,8 @@
 {
 	stasis_app_unregister_event_sources();
 
+	cli_cleanup();
+
 	messaging_cleanup();
 
 	cleanup();
@@ -2104,6 +2112,11 @@
 		return AST_MODULE_LOAD_FAILURE;
 	}
 
+	if (cli_init()) {
+		unload_module();
+		return AST_MODULE_LOAD_FAILURE;
+	}
+
 	bridge_stasis_init();
 
 	stasis_app_register_event_sources();
diff --git a/res/stasis/app.c b/res/stasis/app.c
index 2689e5e..b2d9529 100644
--- a/res/stasis/app.c
+++ b/res/stasis/app.c
@@ -32,6 +32,7 @@
 #include "messaging.h"
 
 #include "asterisk/callerid.h"
+#include "asterisk/cli.h"
 #include "asterisk/stasis_app.h"
 #include "asterisk/stasis_bridges.h"
 #include "asterisk/stasis_channels.h"
@@ -61,6 +62,8 @@
 	void *data;
 	/*! Subscription model for the application */
 	enum stasis_app_subscription_model subscription_model;
+	/*! Whether or not someone wants to see debug messages about this app */
+	int debug;
 	/*! Name of the Stasis application */
 	char name[];
 };
@@ -833,6 +836,18 @@
 	}
 }
 
+void app_set_debug(struct stasis_app *app, int debug)
+{
+	if (!app) {
+		return;
+	}
+
+	{
+		SCOPED_AO2LOCK(lock, app);
+		app->debug = debug;
+	}
+}
+
 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);
@@ -930,6 +945,7 @@
 void app_send(struct stasis_app *app, struct ast_json *message)
 {
 	stasis_app_cb handler;
+	int debug;
 	char eid[20];
 	RAII_VAR(void *, data, NULL, ao2_cleanup);
 
@@ -942,12 +958,20 @@
 	/* Copy off mutable state with lock held */
 	{
 		SCOPED_AO2LOCK(lock, app);
+		debug = app->debug;
 		handler = app->handler;
 		if (app->data) {
 			ao2_ref(app->data, +1);
 			data = app->data;
 		}
 		/* Name is immutable; no need to copy */
+	}
+
+	if (debug) {
+		char *dump = ast_json_dump_string_format(message, AST_JSON_PRETTY);
+		ast_verb(0, "Dispatching message to Stasis app '%s':\n%s\n",
+			app->name, dump);
+		ast_json_free(dump);
 	}
 
 	if (!handler) {
@@ -1027,6 +1051,73 @@
 	return app->name;
 }
 
+static int forwards_filter_by_type(void *obj, void *arg, int flags)
+{
+	struct app_forwards *forward = obj;
+	enum forward_type *forward_type = arg;
+
+	if (forward->forward_type == *forward_type) {
+		return CMP_MATCH;
+	}
+
+	return 0;
+}
+
+void app_to_cli(const struct stasis_app *app, struct ast_cli_args *a)
+{
+	struct ao2_iterator *channels;
+	struct ao2_iterator *endpoints;
+	struct ao2_iterator *bridges;
+	struct app_forwards *forward;
+	enum forward_type forward_type;
+
+	ast_cli(a->fd, "Name: %s\n"
+		"  Debug: %s\n"
+		"  Subscription Model: %s\n",
+		app->name,
+		app->debug ? "Yes" : "No",
+		app->subscription_model == STASIS_APP_SUBSCRIBE_ALL ?
+			"Global Resource Subscription" :
+			"Application/Explicit Resource Subscription");
+	ast_cli(a->fd, "  Subscriptions: %d\n", ao2_container_count(app->forwards));
+
+	ast_cli(a->fd, "    Channels:\n");
+	forward_type = FORWARD_CHANNEL;
+	channels = ao2_callback(app->forwards, OBJ_MULTIPLE,
+		forwards_filter_by_type, &forward_type);
+	if (channels) {
+		while ((forward = ao2_iterator_next(channels))) {
+			ast_cli(a->fd, "      %s (%d)\n", forward->id, forward->interested);
+			ao2_ref(forward, -1);
+		}
+		ao2_iterator_destroy(channels);
+	}
+
+	ast_cli(a->fd, "    Bridges:\n");
+	forward_type = FORWARD_BRIDGE;
+	bridges = ao2_callback(app->forwards, OBJ_MULTIPLE,
+		forwards_filter_by_type, &forward_type);
+	if (bridges) {
+		while ((forward = ao2_iterator_next(bridges))) {
+			ast_cli(a->fd, "      %s (%d)\n", forward->id, forward->interested);
+			ao2_ref(forward, -1);
+		}
+		ao2_iterator_destroy(bridges);
+	}
+
+	ast_cli(a->fd, "    Endpoints:\n");
+	forward_type = FORWARD_ENDPOINT;
+	endpoints = ao2_callback(app->forwards, OBJ_MULTIPLE,
+		forwards_filter_by_type, &forward_type);
+	if (endpoints) {
+		while ((forward = ao2_iterator_next(endpoints))) {
+			ast_cli(a->fd, "      %s (%d)\n", forward->id, forward->interested);
+			ao2_ref(forward, -1);
+		}
+		ao2_iterator_destroy(endpoints);
+	}
+}
+
 struct ast_json *app_to_json(const struct stasis_app *app)
 {
 	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
diff --git a/res/stasis/app.h b/res/stasis/app.h
index 2c8db1c..6ed6a29 100644
--- a/res/stasis/app.h
+++ b/res/stasis/app.h
@@ -127,7 +127,25 @@
 
 struct app_forwards;
 
+/*!
+ * \brief Create a JSON representation of a \c stasis_app
+ *
+ * \param app The application
+ *
+ * \return \c JSON blob on success
+ * \return \c NULL on error
+ */
 struct ast_json *app_to_json(const struct stasis_app *app);
+
+struct ast_cli_args;
+
+/*!
+ * \brief Dump properties of a \c stasis_app to the CLI
+ *
+ * \param app The application
+ * \param a The CLI arguments
+ */
+void app_to_cli(const struct stasis_app *app, struct ast_cli_args *a);
 
 /*!
  * \brief Subscribes an application to a channel.
@@ -282,4 +300,12 @@
  */
 int app_send_end_msg(struct stasis_app *app, struct ast_channel *chan);
 
+/*!
+ * \brief Enable/disable debugging on an application
+ *
+ * \param app The app to debug
+ * \param debug If non-zero, enable debugging. If zero, disable.
+ */
+void app_set_debug(struct stasis_app *app, int debug);
+
 #endif /* _ASTERISK_RES_STASIS_APP_H */
diff --git a/res/stasis/cli.c b/res/stasis/cli.c
new file mode 100644
index 0000000..f1dee55
--- /dev/null
+++ b/res/stasis/cli.c
@@ -0,0 +1,216 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, Digium, Inc.
+ *
+ * Matt Jordan <mjordan at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Stasis CLI commands.
+ *
+ * \author Matt Jordan <mjordan at digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+
+#include "cli.h"
+#include "app.h"
+
+
+static char *ari_show_apps(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ao2_container *apps;
+	struct ao2_iterator it_apps;
+	char *app;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "ari show apps";
+		e->usage =
+			"Usage: ari show apps\n"
+			"       Lists all registered applications.\n"
+			;
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	default:
+		break;
+	}
+
+	if (a->argc != 3) {
+		return CLI_SHOWUSAGE;
+	}
+
+	apps = stasis_app_get_all();
+	if (!apps) {
+		ast_cli(a->fd, "Unable to retrieve registered applications!\n");
+		return CLI_FAILURE;
+	}
+
+	ast_cli(a->fd, "Application Name         \n");
+	ast_cli(a->fd, "=========================\n");
+	it_apps = ao2_iterator_init(apps, 0);
+	while ((app = ao2_iterator_next(&it_apps))) {
+		ast_cli(a->fd, "%-25.25s\n", app);
+		ao2_ref(app, -1);
+	}
+
+	ao2_iterator_destroy(&it_apps);
+	ao2_ref(apps, -1);
+
+	return CLI_SUCCESS;
+}
+
+struct app_complete {
+	/*! Nth app to search for */
+	int state;
+	/*! Which app currently on */
+	int which;
+};
+
+static int complete_ari_app_search(void *obj, void *arg, void *data, int flags)
+{
+	struct app_complete *search = data;
+
+	if (++search->which > search->state) {
+		return CMP_MATCH;
+	}
+	return 0;
+}
+
+static char *complete_ari_app(struct ast_cli_args *a)
+{
+	RAII_VAR(struct ao2_container *, apps, stasis_app_get_all(), ao2_cleanup);
+	RAII_VAR(char *, app, NULL, ao2_cleanup);
+
+	struct app_complete search = {
+		.state = a->n,
+	};
+
+	if (!apps) {
+		ast_cli(a->fd, "Error getting ARI applications\n");
+		return CLI_FAILURE;
+	}
+
+	app = ao2_callback_data(apps,
+		ast_strlen_zero(a->word) ? 0 : OBJ_PARTIAL_KEY,
+		complete_ari_app_search, (char*)a->word, &search);
+
+	return app ? ast_strdup(app) : NULL;
+}
+
+static char *complete_ari_show_app(struct ast_cli_args *a)
+{
+	if (a->pos == 3) {
+		return complete_ari_app(a);
+	}
+
+	return NULL;
+}
+
+static char *ari_show_app(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	void *app;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "ari show app";
+		e->usage =
+			"Usage: ari show app <application>\n"
+			"       Provide detailed information about a registered application.\n"
+			;
+		return NULL;
+	case CLI_GENERATE:
+		return complete_ari_show_app(a);
+	default:
+		break;
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	app = stasis_app_get_by_name(a->argv[3]);
+	if (!app) {
+		return CLI_FAILURE;
+	}
+
+	app_to_cli(app, a);
+
+	ao2_ref(app, -1);
+
+	return CLI_SUCCESS;
+}
+
+static char *ari_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	void *app;
+	int debug;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "ari set debug";
+		e->usage =
+			"Usage: ari set debug <application> <on|off>\n"
+			"       Enable or disable debugging on a specific application.\n"
+			;
+		return NULL;
+	case CLI_GENERATE:
+		return complete_ari_show_app(a);
+	default:
+		break;
+	}
+
+	if (a->argc != 5) {
+		return CLI_SHOWUSAGE;
+	}
+
+	app = stasis_app_get_by_name(a->argv[3]);
+	if (!app) {
+		return CLI_FAILURE;
+	}
+
+	debug = !strcmp(a->argv[4], "on");
+	app_set_debug(app, debug);
+	ast_cli(a->fd, "Debugging on '%s' %s\n",
+		app_name(app),
+		debug ? "enabled" : "disabled");
+
+	ao2_ref(app, -1);
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_ari[] = {
+	AST_CLI_DEFINE(ari_show_apps, "List registered ARI applications"),
+	AST_CLI_DEFINE(ari_show_app, "Display details of a registered ARI application"),
+	AST_CLI_DEFINE(ari_set_debug, "Enable/disable debugging of an ARI application"),
+};
+
+
+int cli_init(void)
+{
+	return ast_cli_register_multiple(cli_ari, ARRAY_LEN(cli_ari));
+}
+
+void cli_cleanup(void)
+{
+	ast_cli_unregister_multiple(cli_ari, ARRAY_LEN(cli_ari));
+}
diff --git a/res/stasis/cli.h b/res/stasis/cli.h
new file mode 100644
index 0000000..49235c7
--- /dev/null
+++ b/res/stasis/cli.h
@@ -0,0 +1,43 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, Digium, Inc.
+ *
+ * Matt Jordan <mjordan at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef _ASTERISK_RES_STASIS_CLI_H
+#define _ASTERISK_RES_STASIS_CLI_H
+
+/*! \file
+ *
+ * \brief Internal API for Stasis application CLI commands
+ *
+ * \author Matt Jordan <mjordan at digium.com>
+ * \since 13.13.0
+ */
+
+/*!
+ * \brief Initialize the CLI commands
+ *
+ * \retval 0 on success
+ * \retval non-zero on error
+ */
+int cli_init(void);
+
+/*!
+ * \brief Cleanup the CLI commands
+ */
+void cli_cleanup(void);
+
+#endif /* _ASTERISK_RES_STASIS_CLI_H */

-- 
To view, visit https://gerrit.asterisk.org/4164
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I51ad6ff0cabee0d69db06858c13f18b1c513c9f5
Gerrit-PatchSet: 4
Gerrit-Project: asterisk
Gerrit-Branch: 13
Gerrit-Owner: Matt Jordan <mjordan at digium.com>
Gerrit-Reviewer: Anonymous Coward #1000019
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Kevin Harwell <kharwell at digium.com>
Gerrit-Reviewer: Mark Michelson <mmichelson at digium.com>
Gerrit-Reviewer: Matt Jordan <mjordan at digium.com>



More information about the asterisk-code-review mailing list