Move more channel events to Stasis; move res_json.c to main/json.c.

This patch started out simply as fixing the bouncing tests introduced
in r382685, but required some other changes to give it a decent
implementation.

To fix the bouncing tests, the UserEvent and Newexten AMI events
needed to be refactored to dispatch via Stasis. Dispatching directly
to AMI resulted in those events sometimes getting ahead of the
associated Newchannel events, which would understandably confuse anyone.

I found that instead of creating a zillion different message types and
structures associated with them, it would be preferable to define a
message type that has a channel snapshot and a blob of structured data
with a small bit of additional information. The JSON object model
provides a very nice way of representing structured data, so I went
with that.

 * Move JSON support from res_json.c to main/json.c
   * Made libjansson-dev a required dependency
 * Added an ast_channel_blob message type, which has a channel
   snapshot and JSON blob of data.
 * Changed UserEvent and Newexten events so that they are dispatched
   via ast_channel_blob messages on the channel's topic.
 * Got rid of the ast_channel_varset message; used ast_channel_blob
   instead.
 * Extracted the manager functions converting Stasis channel events to
   AMI events into manager_channel.c.

(issue ASTERISK-21096)
Review: https://reviewboard.asterisk.org/r/2381/


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@383579 65c4cc65-6c06-0410-ace0-fbb531ad65f3
changes/78/78/1
David M. Lee 13 years ago
parent 401f7c1880
commit cf9324b25e

@ -41,6 +41,15 @@ AMI (Asterisk Manager Interface)
mechanisms (such as the Playback application), the audio can be stopped, mechanisms (such as the Playback application), the audio can be stopped,
reversed, or skipped forward. reversed, or skipped forward.
* Channel related events now contain a snapshot of channel state, adding new
fields to many of these events.
* The AMI event 'Newexten' field 'Extension' is deprecated, and may be removed
in a future release. Please use the common 'Exten' field instead.
* The AMI event 'UserEvent' from app_userevent now contains the channel state
fields. The channel state fields will come before the body fields.
Channel Drivers Channel Drivers
------------------ ------------------

@ -33,6 +33,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/module.h" #include "asterisk/module.h"
#include "asterisk/manager.h" #include "asterisk/manager.h"
#include "asterisk/app.h" #include "asterisk/app.h"
#include "asterisk/json.h"
/*** DOCUMENTATION /*** DOCUMENTATION
<application name="UserEvent" language="en_US"> <application name="UserEvent" language="en_US">
@ -68,11 +69,12 @@ static int userevent_exec(struct ast_channel *chan, const char *data)
AST_APP_ARG(eventname); AST_APP_ARG(eventname);
AST_APP_ARG(extra)[100]; AST_APP_ARG(extra)[100];
); );
struct ast_str *body = ast_str_create(16); RAII_VAR(struct ast_str *, body, ast_str_create(16), ast_free);
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
if (ast_strlen_zero(data)) { if (ast_strlen_zero(data)) {
ast_log(LOG_WARNING, "UserEvent requires an argument (eventname,optional event body)\n"); ast_log(LOG_WARNING, "UserEvent requires an argument (eventname,optional event body)\n");
ast_free(body);
return -1; return -1;
} }
@ -89,24 +91,21 @@ static int userevent_exec(struct ast_channel *chan, const char *data)
ast_str_append(&body, 0, "%s\r\n", args.extra[x]); ast_str_append(&body, 0, "%s\r\n", args.extra[x]);
} }
/*** DOCUMENTATION blob = ast_json_pack("{s: s, s: s, s: s}",
<managerEventInstance> "type", "userevent",
<synopsis>A user defined event raised from the dialplan.</synopsis> "eventname", args.eventname,
<parameter name="UserEvent"> "body", ast_str_buffer(body));
<para>The event name, as specified in the dialplan.</para> if (!blob) {
</parameter> ast_log(LOG_WARNING, "Unable to create message buffer\n");
<see-also> return -1;
<ref type="application">UserEvent</ref> }
</see-also>
</managerEventInstance> msg = ast_channel_blob_create(chan, blob);
***/ if (!msg) {
manager_event(EVENT_FLAG_USER, "UserEvent", return -1;
"UserEvent: %s\r\n" }
"Uniqueid: %s\r\n"
"%s",
args.eventname, ast_channel_uniqueid(chan), ast_str_buffer(body));
ast_free(body); stasis_publish(ast_channel_topic(chan), msg);
return 0; return 0;
} }

1644
configure vendored

File diff suppressed because it is too large Load Diff

@ -491,12 +491,13 @@ AC_HEADER_STDC
AC_HEADER_SYS_WAIT AC_HEADER_SYS_WAIT
AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h libintl.h limits.h locale.h malloc.h netdb.h netinet/in.h stddef.h stdint.h stdlib.h string.h strings.h sys/event.h sys/file.h sys/ioctl.h sys/param.h sys/socket.h sys/time.h syslog.h termios.h unistd.h utime.h arpa/nameser.h sys/io.h]) AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h libintl.h limits.h locale.h malloc.h netdb.h netinet/in.h stddef.h stdint.h stdlib.h string.h strings.h sys/event.h sys/file.h sys/ioctl.h sys/param.h sys/socket.h sys/time.h syslog.h termios.h unistd.h utime.h arpa/nameser.h sys/io.h])
# Any one of these 5 packages support a mandatory requirement, so we want to check on them as early as possible. # Any one of these packages support a mandatory requirement, so we want to check on them as early as possible.
AST_EXT_LIB_CHECK([TERMCAP], [termcap], [tgetent], []) AST_EXT_LIB_CHECK([TERMCAP], [termcap], [tgetent], [])
AST_EXT_LIB_CHECK([TINFO], [tinfo], [tgetent], []) AST_EXT_LIB_CHECK([TINFO], [tinfo], [tgetent], [])
AST_EXT_LIB_CHECK([CURSES], [curses], [initscr], [curses.h]) AST_EXT_LIB_CHECK([CURSES], [curses], [initscr], [curses.h])
AST_EXT_LIB_CHECK([NCURSES], [ncurses], [initscr], [curses.h]) AST_EXT_LIB_CHECK([NCURSES], [ncurses], [initscr], [curses.h])
AST_EXT_LIB_CHECK([UUID], [uuid], [uuid_generate_random], [uuid/uuid.h], [-luuid]) AST_EXT_LIB_CHECK([UUID], [uuid], [uuid_generate_random], [uuid/uuid.h], [-luuid])
AST_EXT_LIB_CHECK([JANSSON], [jansson], [json_dumps], [jansson.h])
EDITLINE_LIB="" EDITLINE_LIB=""
if test "x$TERMCAP_LIB" != "x" ; then if test "x$TERMCAP_LIB" != "x" ; then
@ -516,6 +517,10 @@ if test "x$UUID_LIB" == "x"; then
AC_MSG_ERROR([*** uuid support not found (this typically means the uuid development package is missing)]) AC_MSG_ERROR([*** uuid support not found (this typically means the uuid development package is missing)])
fi fi
if test "x$JANSSON_LIB" == "x"; then
AC_MSG_ERROR([*** JSON support not found (this typically means the libjansson development package is missing)])
fi
# Another mandatory item (unless it's explicitly disabled) # Another mandatory item (unless it's explicitly disabled)
AC_ARG_ENABLE([xmldoc], AC_ARG_ENABLE([xmldoc],
[AS_HELP_STRING([--disable-xmldoc], [AS_HELP_STRING([--disable-xmldoc],
@ -1869,8 +1874,6 @@ AST_EXT_LIB_CHECK([INOTIFY], [c], [inotify_init], [sys/inotify.h])
AST_EXT_LIB_CHECK([JACK], [jack], [jack_activate], [jack/jack.h]) AST_EXT_LIB_CHECK([JACK], [jack], [jack_activate], [jack/jack.h])
AST_EXT_LIB_CHECK([JANSSON], [jansson], [json_dumps], [jansson.h])
# BSD (and OS X) equivalent of inotify # BSD (and OS X) equivalent of inotify
AST_EXT_LIB_CHECK([KQUEUE], [c], [kqueue], [sys/event.h]) AST_EXT_LIB_CHECK([KQUEUE], [c], [kqueue], [sys/event.h])

@ -1229,6 +1229,11 @@
/* Define to 1 if running on Darwin. */ /* Define to 1 if running on Darwin. */
#undef _DARWIN_UNLIMITED_SELECT #undef _DARWIN_UNLIMITED_SELECT
/* Enable large inode numbers on Mac OS X 10.5. */
#ifndef _DARWIN_USE_64_BIT_INODE
# define _DARWIN_USE_64_BIT_INODE 1
#endif
/* Number of bits in a file offset, on hosts where this is settable. */ /* Number of bits in a file offset, on hosts where this is settable. */
#undef _FILE_OFFSET_BITS #undef _FILE_OFFSET_BITS

@ -151,6 +151,7 @@ extern "C" {
#include "asterisk/ccss.h" #include "asterisk/ccss.h"
#include "asterisk/framehook.h" #include "asterisk/framehook.h"
#include "asterisk/stasis.h" #include "asterisk/stasis.h"
#include "asterisk/json.h"
#define DATASTORE_INHERIT_FOREVER INT_MAX #define DATASTORE_INHERIT_FOREVER INT_MAX
@ -4187,24 +4188,51 @@ struct stasis_caching_topic *ast_channel_topic_all_cached(void);
/*! /*!
* \since 12 * \since 12
* \brief Variable set event. * \brief Blob of data associated with a channel.
*
* The \c blob is actually a JSON object of structured data. It has a "type" field
* which contains the type string describing this blob.
*/ */
struct ast_channel_varset { struct ast_channel_blob {
/*! Channel variable was set on (or NULL for global variable) */ /*! Channel blob is associated with (or NULL for global/all channels) */
struct ast_channel_snapshot *snapshot; struct ast_channel_snapshot *snapshot;
/*! Variable name */ /*! JSON blob of data */
char *variable; struct ast_json *blob;
/*! New value */
char *value;
}; };
/*! /*!
* \since 12 * \since 12
* \brief Message type for \ref ast_channel_varset messages. * \brief Message type for \ref ast_channel_blob messages.
*
* \retval Message type for \ref ast_channel_blob messages.
*/
struct stasis_message_type *ast_channel_blob(void);
/*!
* \since 12
* \brief Extracts the type field from a \ref ast_channel_blob.
* Returned \c char* is still owned by \a obj
* \param obj Channel blob object.
* \return Type field value from the blob.
* \return \c NULL on error.
*/
const char *ast_channel_blob_type(struct ast_channel_blob *obj);
/*!
* \since 12
* \brief Creates a \ref ast_channel_blob message.
*
* The \a blob JSON object requires a \c "type" field describing the blob. It
* should also be treated as immutable and not modified after it is put into the
* message.
* *
* \retval Message type for \ref ast_channel_varset messages. * \param chan Channel blob is associated with, or NULL for global/all channels.
* \param blob JSON object representing the data.
* \return \ref ast_channel_blob message.
* \return \c NULL on error
*/ */
struct stasis_message_type *ast_channel_varset(void); struct stasis_message *ast_channel_blob_create(struct ast_channel *chan,
struct ast_json *blob);
/*! /*!
* \since 12 * \since 12

@ -316,4 +316,12 @@ int astman_datastore_remove(struct mansession *s, struct ast_datastore *datastor
*/ */
struct ast_datastore *astman_datastore_find(struct mansession *s, const struct ast_datastore_info *info, const char *uid); struct ast_datastore *astman_datastore_find(struct mansession *s, const struct ast_datastore_info *info, const char *uid);
/*!
* \brief Initialize support for AMI channel events.
* \return 0 on success.
* \return non-zero on error.
* \since 12
*/
int manager_channels_init(void);
#endif /* _ASTERISK_MANAGER_H */ #endif /* _ASTERISK_MANAGER_H */

@ -155,7 +155,7 @@ static struct ao2_container *channels;
/*! \brief Message type for channel snapshot events */ /*! \brief Message type for channel snapshot events */
static struct stasis_message_type *__channel_snapshot; static struct stasis_message_type *__channel_snapshot;
static struct stasis_message_type *__channel_varset; static struct stasis_message_type *__channel_blob;
struct stasis_topic *__channel_topic_all; struct stasis_topic *__channel_topic_all;
@ -243,37 +243,79 @@ static void publish_channel_state(struct ast_channel *chan)
stasis_publish(ast_channel_topic(chan), message); stasis_publish(ast_channel_topic(chan), message);
} }
static void channel_varset_dtor(void *obj) static void channel_blob_dtor(void *obj)
{ {
struct ast_channel_varset *event = obj; struct ast_channel_blob *event = obj;
ao2_cleanup(event->snapshot); ao2_cleanup(event->snapshot);
ast_free(event->variable); ast_json_unref(event->blob);
ast_free(event->value);
} }
void ast_channel_publish_varset(struct ast_channel *chan, const char *name, const char *value) struct stasis_message *ast_channel_blob_create(struct ast_channel *chan,
struct ast_json *blob)
{ {
RAII_VAR(struct ast_channel_varset *, event, NULL, ao2_cleanup); RAII_VAR(struct ast_channel_blob *, obj, NULL, ao2_cleanup);
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
struct ast_json *type;
event = ao2_alloc(sizeof(*event), channel_varset_dtor); ast_assert(blob != NULL);
if (!event) {
return; type = ast_json_object_get(blob, "type");
if (type == NULL) {
ast_log(LOG_ERROR, "Invalid ast_channel_blob; missing type field");
return NULL;
}
obj = ao2_alloc(sizeof(*obj), channel_blob_dtor);
if (!obj) {
return NULL;
} }
if (chan) { if (chan) {
event->snapshot = ast_channel_snapshot_create(chan); obj->snapshot = ast_channel_snapshot_create(chan);
if (event->snapshot == NULL) { if (obj->snapshot == NULL) {
return; return NULL;
} }
} }
event->variable = ast_strdup(name);
event->value = ast_strdup(value); obj->blob = ast_json_ref(blob);
if (event->variable == NULL || event->value == NULL) {
msg = stasis_message_create(ast_channel_blob(), obj);
if (!msg) {
return NULL;
}
ao2_ref(msg, +1);
return msg;
}
const char *ast_channel_blob_type(struct ast_channel_blob *obj)
{
if (obj == NULL) {
return NULL;
}
return ast_json_string_get(ast_json_object_get(obj->blob, "type"));
}
void ast_channel_publish_varset(struct ast_channel *chan, const char *name, const char *value)
{
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
ast_assert(name != NULL);
ast_assert(value != NULL);
blob = ast_json_pack("{s: s, s: s, s: s}",
"type", "varset",
"variable", name,
"value", value);
if (!blob) {
ast_log(LOG_ERROR, "Error creating message\n");
return; return;
} }
msg = stasis_message_create(ast_channel_varset(), event); msg = ast_channel_blob_create(chan, ast_json_ref(blob));
if (!msg) { if (!msg) {
return; return;
} }
@ -8633,8 +8675,8 @@ static void channels_shutdown(void)
{ {
ao2_cleanup(__channel_snapshot); ao2_cleanup(__channel_snapshot);
__channel_snapshot = NULL; __channel_snapshot = NULL;
ao2_cleanup(__channel_varset); ao2_cleanup(__channel_blob);
__channel_varset = NULL; __channel_blob = NULL;
ao2_cleanup(__channel_topic_all); ao2_cleanup(__channel_topic_all);
__channel_topic_all = NULL; __channel_topic_all = NULL;
__channel_topic_all_cached = stasis_caching_unsubscribe(__channel_topic_all_cached); __channel_topic_all_cached = stasis_caching_unsubscribe(__channel_topic_all_cached);
@ -8666,7 +8708,7 @@ void ast_channels_init(void)
} }
__channel_snapshot = stasis_message_type_create("ast_channel_snapshot"); __channel_snapshot = stasis_message_type_create("ast_channel_snapshot");
__channel_varset = stasis_message_type_create("ast_channel_varset"); __channel_blob = stasis_message_type_create("ast_channel_blob");
__channel_topic_all = stasis_topic_create("ast_channel_topic_all"); __channel_topic_all = stasis_topic_create("ast_channel_topic_all");
__channel_topic_all_cached = stasis_caching_topic_create(__channel_topic_all, channel_snapshot_get_id); __channel_topic_all_cached = stasis_caching_topic_create(__channel_topic_all, channel_snapshot_get_id);
@ -11305,9 +11347,9 @@ struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *cha
return snapshot; return snapshot;
} }
struct stasis_message_type *ast_channel_varset(void) struct stasis_message_type *ast_channel_blob(void)
{ {
return __channel_varset; return __channel_blob;
} }
struct stasis_message_type *ast_channel_snapshot(void) struct stasis_message_type *ast_channel_snapshot(void)

@ -964,73 +964,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
manager.conf will be present upon starting a new session.</para> manager.conf will be present upon starting a new session.</para>
</description> </description>
</manager> </manager>
<managerEvent language="en_US" name="Newchannel">
<managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when a new channel is created.</synopsis>
<syntax>
<parameter name="Channel">
</parameter>
<parameter name="ChannelState">
<para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
</parameter>
<parameter name="ChannelStateDesc">
<enumlist>
<enum name="Down"/>
<enum name="Rsrvd"/>
<enum name="OffHook"/>
<enum name="Dialing"/>
<enum name="Ring"/>
<enum name="Ringing"/>
<enum name="Up"/>
<enum name="Busy"/>
<enum name="Dialing Offhook"/>
<enum name="Pre-ring"/>
<enum name="Unknown"/>
</enumlist>
</parameter>
<parameter name="CallerIDNum">
</parameter>
<parameter name="CallerIDName">
</parameter>
<parameter name="ConnectedLineNum">
</parameter>
<parameter name="ConnectedLineName">
</parameter>
<parameter name="AccountCode">
</parameter>
<parameter name="Context">
</parameter>
<parameter name="Exten">
</parameter>
<parameter name="Priority">
</parameter>
<parameter name="Uniqueid">
</parameter>
<parameter name="Cause">
<para>A numeric cause code for why the channel was hung up.</para>
</parameter>
<parameter name="Cause-txt">
<para>A description of why the channel was hung up.</para>
</parameter>
</syntax>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="Newstate">
<managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when a channel's state changes.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
</syntax>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="Hangup">
<managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when a channel is hung up.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
</syntax>
</managerEventInstance>
</managerEvent>
***/ ***/
/*! \addtogroup Group_AMI AMI functions /*! \addtogroup Group_AMI AMI functions
@ -1128,8 +1061,6 @@ static const struct {
{{ "restart", "gracefully", NULL }}, {{ "restart", "gracefully", NULL }},
}; };
static struct stasis_subscription *channel_state_sub;
static void acl_change_event_cb(const struct ast_event *event, void *userdata); static void acl_change_event_cb(const struct ast_event *event, void *userdata);
static void acl_change_event_subscribe(void) static void acl_change_event_subscribe(void)
@ -7446,127 +7377,6 @@ static void load_channelvars(struct ast_variable *var)
AST_RWLIST_UNLOCK(&channelvars); AST_RWLIST_UNLOCK(&channelvars);
} }
/*!
* \brief Generate the AMI message body from a channel snapshot
* \internal
*
* \param snapshot the channel snapshot for which to generate an AMI message body
*
* \retval NULL on error
* \retval ast_str* on success (must be ast_freed by caller)
*/
static struct ast_str *manager_build_channel_state_string(const struct ast_channel_snapshot *snapshot)
{
struct ast_str *out = ast_str_create(1024);
int res = 0;
if (!out) {
return NULL;
}
res = ast_str_set(&out, 0,
"Channel: %s\r\n"
"ChannelState: %d\r\n"
"ChannelStateDesc: %s\r\n"
"CallerIDNum: %s\r\n"
"CallerIDName: %s\r\n"
"ConnectedLineNum: %s\r\n"
"ConnectedLineName: %s\r\n"
"AccountCode: %s\r\n"
"Context: %s\r\n"
"Exten: %s\r\n"
"Priority: %d\r\n"
"Uniqueid: %s\r\n"
"Cause: %d\r\n"
"Cause-txt: %s\r\n",
snapshot->name,
snapshot->state,
ast_state2str(snapshot->state),
snapshot->caller_number,
snapshot->caller_name,
snapshot->connected_number,
snapshot->connected_name,
snapshot->accountcode,
snapshot->context,
snapshot->exten,
snapshot->priority,
snapshot->uniqueid,
snapshot->hangupcause,
ast_cause2str(snapshot->hangupcause));
if (!res) {
return NULL;
}
return out;
}
static void channel_snapshot_update(struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot)
{
int is_hungup;
char *manager_event = NULL;
if (!new_snapshot) {
/* Ignore cache clearing events; we'll see the hangup first */
return;
}
is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0;
if (!old_snapshot) {
manager_event = "Newchannel";
}
if (old_snapshot && old_snapshot->state != new_snapshot->state) {
manager_event = "Newstate";
}
if (old_snapshot && is_hungup) {
manager_event = "Hangup";
}
if (manager_event) {
RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
channel_event_string = manager_build_channel_state_string(new_snapshot);
if (channel_event_string) {
manager_event(EVENT_FLAG_CALL, manager_event, "%s", ast_str_buffer(channel_event_string));
}
}
}
static void channel_varset(const char *channel_name, const char *uniqueid, const char *name, const char *value)
{
/*** DOCUMENTATION
<managerEventInstance>
<synopsis>Raised when a variable is set to a particular value.</synopsis>
</managerEventInstance>
***/
manager_event(EVENT_FLAG_DIALPLAN, "VarSet",
"Channel: %s\r\n"
"Variable: %s\r\n"
"Value: %s\r\n"
"Uniqueid: %s\r\n",
channel_name, name, value, uniqueid);
}
static void channel_event_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
{
if (stasis_message_type(message) == stasis_cache_update()) {
struct stasis_cache_update *update = stasis_message_data(message);
if (ast_channel_snapshot() == update->type) {
struct ast_channel_snapshot *old_snapshot =
stasis_message_data(update->old_snapshot);
struct ast_channel_snapshot *new_snapshot =
stasis_message_data(update->new_snapshot);
channel_snapshot_update(old_snapshot, new_snapshot);
}
} else if (stasis_message_type(message) == ast_channel_varset()) {
struct ast_channel_varset *varset = stasis_message_data(message);
const char *name = varset->snapshot ? varset->snapshot->name : "none";
const char *uniqueid = varset->snapshot ? varset->snapshot->uniqueid : "none";
channel_varset(name, uniqueid, varset->variable, varset->value);
}
}
/*! \internal \brief Free a user record. Should already be removed from the list */ /*! \internal \brief Free a user record. Should already be removed from the list */
static void manager_free_user(struct ast_manager_user *user) static void manager_free_user(struct ast_manager_user *user)
{ {
@ -7590,8 +7400,6 @@ static void manager_shutdown(void)
{ {
struct ast_manager_user *user; struct ast_manager_user *user;
channel_state_sub = stasis_unsubscribe(channel_state_sub);
if (registered) { if (registered) {
ast_manager_unregister("Ping"); ast_manager_unregister("Ping");
ast_manager_unregister("Events"); ast_manager_unregister("Events");
@ -7683,10 +7491,8 @@ static int __init_manager(int reload, int by_external_config)
manager_enabled = 0; manager_enabled = 0;
if (!channel_state_sub) { if (manager_channels_init()) {
channel_state_sub = stasis_subscribe( return -1;
stasis_caching_get_topic(ast_channel_topic_all_cached()),
channel_event_cb, NULL);
} }
if (!registered) { if (!registered) {

@ -0,0 +1,409 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2013, Digium, Inc.
*
* David M. Lee, II <dlee@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 The Asterisk Management Interface - AMI (channel event handling)
*
* \author David M. Lee, II <dlee@digium.com>
*
* AMI generated many per-channel and global-channel events by converting Stasis
* messages to AMI events. It makes sense to simply put them into a single file.
*/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/channel.h"
#include "asterisk/manager.h"
#include "asterisk/stasis_message_router.h"
#include "asterisk/pbx.h"
static struct stasis_message_router *channel_state_router;
/*** DOCUMENTATION
<managerEvent language="en_US" name="Newchannel">
<managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when a new channel is created.</synopsis>
<syntax>
<parameter name="Channel">
</parameter>
<parameter name="ChannelState">
<para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
</parameter>
<parameter name="ChannelStateDesc">
<enumlist>
<enum name="Down"/>
<enum name="Rsrvd"/>
<enum name="OffHook"/>
<enum name="Dialing"/>
<enum name="Ring"/>
<enum name="Ringing"/>
<enum name="Up"/>
<enum name="Busy"/>
<enum name="Dialing Offhook"/>
<enum name="Pre-ring"/>
<enum name="Unknown"/>
</enumlist>
</parameter>
<parameter name="CallerIDNum">
</parameter>
<parameter name="CallerIDName">
</parameter>
<parameter name="ConnectedLineNum">
</parameter>
<parameter name="ConnectedLineName">
</parameter>
<parameter name="AccountCode">
</parameter>
<parameter name="Context">
</parameter>
<parameter name="Exten">
</parameter>
<parameter name="Priority">
</parameter>
<parameter name="Uniqueid">
</parameter>
<parameter name="Cause">
<para>A numeric cause code for why the channel was hung up.</para>
</parameter>
<parameter name="Cause-txt">
<para>A description of why the channel was hung up.</para>
</parameter>
</syntax>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="Newstate">
<managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when a channel's state changes.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
</syntax>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="Hangup">
<managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when a channel is hung up.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
</syntax>
</managerEventInstance>
</managerEvent>
***/
/*!
* \brief Generate the AMI message body from a channel snapshot
* \internal
*
* \param snapshot the channel snapshot for which to generate an AMI message
* body
*
* \retval NULL on error
* \retval ast_str* on success (must be ast_freed by caller)
*/
static struct ast_str *manager_build_channel_state_string(
const struct ast_channel_snapshot *snapshot)
{
struct ast_str *out = ast_str_create(1024);
int res = 0;
if (!out) {
return NULL;
}
res = ast_str_set(&out, 0,
"Channel: %s\r\n"
"ChannelState: %d\r\n"
"ChannelStateDesc: %s\r\n"
"CallerIDNum: %s\r\n"
"CallerIDName: %s\r\n"
"ConnectedLineNum: %s\r\n"
"ConnectedLineName: %s\r\n"
"AccountCode: %s\r\n"
"Context: %s\r\n"
"Exten: %s\r\n"
"Priority: %d\r\n"
"Uniqueid: %s\r\n"
"Cause: %d\r\n"
"Cause-txt: %s\r\n",
snapshot->name,
snapshot->state,
ast_state2str(snapshot->state),
snapshot->caller_number,
snapshot->caller_name,
snapshot->connected_number,
snapshot->connected_name,
snapshot->accountcode,
snapshot->context,
snapshot->exten,
snapshot->priority,
snapshot->uniqueid,
snapshot->hangupcause,
ast_cause2str(snapshot->hangupcause));
if (!res) {
return NULL;
}
return out;
}
static inline int cep_has_changed(
const struct ast_channel_snapshot *old_snapshot,
const struct ast_channel_snapshot *new_snapshot)
{
ast_assert(old_snapshot != NULL);
ast_assert(new_snapshot != NULL);
return old_snapshot->priority != new_snapshot->priority ||
strcmp(old_snapshot->context, new_snapshot->context) != 0 ||
strcmp(old_snapshot->exten, new_snapshot->exten) != 0;
}
static void channel_snapshot_update(void *data, struct stasis_subscription *sub,
struct stasis_topic *topic,
struct stasis_message *message)
{
RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
struct stasis_cache_update *update = stasis_message_data(message);
struct ast_channel_snapshot *old_snapshot;
struct ast_channel_snapshot *new_snapshot;
int is_hungup, was_hungup;
char *manager_event = NULL;
int new_exten;
if (ast_channel_snapshot() != update->type) {
return;
}
old_snapshot = stasis_message_data(update->old_snapshot);
new_snapshot = stasis_message_data(update->new_snapshot);
if (!new_snapshot) {
/* Ignore cache clearing events; we'll see the hangup first */
return;
}
was_hungup = (old_snapshot && ast_test_flag(&old_snapshot->flags, AST_FLAG_ZOMBIE)) ? 1 : 0;
is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0;
if (!old_snapshot) {
manager_event = "Newchannel";
}
if (old_snapshot && old_snapshot->state != new_snapshot->state) {
manager_event = "Newstate";
}
if (!was_hungup && is_hungup) {
manager_event = "Hangup";
}
/* Detect Newexten transitions
* - if new snapshot has an application set AND
* - first snapshot OR
* - if the old snapshot has no application (first Newexten) OR
* - if the context/priority/exten changes
*/
new_exten = !ast_strlen_zero(new_snapshot->appl) && (
!old_snapshot ||
ast_strlen_zero(old_snapshot->appl) ||
cep_has_changed(old_snapshot, new_snapshot));
if (manager_event || new_exten) {
channel_event_string =
manager_build_channel_state_string(new_snapshot);
}
if (!channel_event_string) {
return;
}
/* Channel state change events */
if (manager_event) {
manager_event(EVENT_FLAG_CALL, manager_event, "%s",
ast_str_buffer(channel_event_string));
}
if (new_exten) {
/* DEPRECATED: Extension field deprecated in 12; remove in 14 */
/*** DOCUMENTATION
<managerEventInstance>
<synopsis>Raised when a channel enters a new context, extension, priority.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
<parameter name="Extension">
<para>Deprecated in 12, but kept for
backward compatability. Please use
'Exten' instead.</para>
</parameter>
<parameter name="Application">
<para>The application about to be executed.</para>
</parameter>
<parameter name="AppData">
<para>The data to be passed to the application.</para>
</parameter>
</syntax>
</managerEventInstance>
***/
manager_event(EVENT_FLAG_DIALPLAN, "Newexten",
"%s"
"Extension: %s\r\n"
"Application: %s\r\n"
"AppData: %s\r\n",
ast_str_buffer(channel_event_string),
new_snapshot->exten,
new_snapshot->appl,
new_snapshot->data);
}
}
static void channel_varset(struct ast_channel_blob *obj)
{
RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
const char *variable = ast_json_string_get(ast_json_object_get(obj->blob, "variable"));
const char *value = ast_json_string_get(ast_json_object_get(obj->blob, "value"));
if (obj->snapshot) {
channel_event_string = manager_build_channel_state_string(obj->snapshot);
} else {
channel_event_string = ast_str_create(35);
ast_str_set(&channel_event_string, 0,
"Channel: none\r\n"
"Uniqueid: none\r\n");
}
if (!channel_event_string) {
return;
}
/*** DOCUMENTATION
<managerEventInstance>
<synopsis>Raised when a variable is set to a particular value.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
<parameter name="Variable">
<para>The variable being set.</para>
</parameter>
<parameter name="Value">
<para>The new value of the variable.</para>
</parameter>
</syntax>
</managerEventInstance>
***/
manager_event(EVENT_FLAG_DIALPLAN, "VarSet",
"%s"
"Variable: %s\r\n"
"Value: %s\r\n",
ast_str_buffer(channel_event_string),
variable, value);
}
static void channel_userevent(struct ast_channel_blob *obj)
{
RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
const char *eventname;
const char *body;
eventname = ast_json_string_get(ast_json_object_get(obj->blob, "eventname"));
body = ast_json_string_get(ast_json_object_get(obj->blob, "body"));
channel_event_string = manager_build_channel_state_string(obj->snapshot);
if (!channel_event_string) {
return;
}
/*** DOCUMENTATION
<managerEventInstance>
<synopsis>A user defined event raised from the dialplan.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
<parameter name="UserEvent">
<para>The event name, as specified in the dialplan.</para>
</parameter>
</syntax>
<see-also>
<ref type="application">UserEvent</ref>
</see-also>
</managerEventInstance>
***/
manager_event(EVENT_FLAG_USER, "UserEvent",
"%s"
"UserEvent: %s\r\n"
"%s",
ast_str_buffer(channel_event_string), eventname, body);
}
/*!
* \brief Callback processing messages on the channel topic.
*/
static void channel_blob_cb(void *data, struct stasis_subscription *sub,
struct stasis_topic *topic,
struct stasis_message *message)
{
struct ast_channel_blob *obj = stasis_message_data(message);
if (strcmp("varset", ast_channel_blob_type(obj)) == 0) {
channel_varset(obj);
} else if (strcmp("userevent", ast_channel_blob_type(obj)) == 0) {
channel_userevent(obj);
}
}
static void manager_channels_shutdown(void)
{
stasis_message_router_unsubscribe(channel_state_router);
channel_state_router = NULL;
}
int manager_channels_init(void)
{
int ret = 0;
if (channel_state_router) {
/* Already initialized */
return 0;
}
ast_register_atexit(manager_channels_shutdown);
channel_state_router = stasis_message_router_create(
stasis_caching_get_topic(ast_channel_topic_all_cached()));
if (!channel_state_router) {
return -1;
}
ret |= stasis_message_router_add(channel_state_router,
stasis_cache_update(),
channel_snapshot_update,
NULL);
ret |= stasis_message_router_add(channel_state_router,
ast_channel_blob(),
channel_blob_cb,
NULL);
/* If somehow we failed to add any routes, just shut down the whole
* things and fail it.
*/
if (ret) {
manager_channels_shutdown();
return -1;
}
return 0;
}

@ -4655,8 +4655,9 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
int res; int res;
struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */ struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
char passdata[EXT_DATA_SIZE]; char passdata[EXT_DATA_SIZE];
int matching_action = (action == E_MATCH || action == E_CANMATCH || action == E_MATCHMORE); int matching_action = (action == E_MATCH || action == E_CANMATCH || action == E_MATCHMORE);
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
ast_rdlock_contexts(); ast_rdlock_contexts();
if (found) if (found)
@ -4700,28 +4701,18 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
COLORIZE(COLOR_BRMAGENTA, 0, passdata), COLORIZE(COLOR_BRMAGENTA, 0, passdata),
"in new stack"); "in new stack");
} }
/*** DOCUMENTATION snapshot = ast_channel_snapshot_create(c);
<managerEventInstance> if (snapshot) {
<synopsis>Raised when a channel enters a new context, extension, priority.</synopsis> /* pbx_exec sets application name and data, but we don't want to log
<syntax> * every exec. Just update the snapshot here instead.
<parameter name="Application"> */
<para>The application about to be executed.</para> ast_string_field_set(snapshot, appl, app->name);
</parameter> ast_string_field_set(snapshot, data, passdata);
<parameter name="AppData"> msg = stasis_message_create(ast_channel_snapshot(), snapshot);
<para>The data to be passed to the application.</para> if (msg) {
</parameter> stasis_publish(ast_channel_topic(c), msg);
</syntax> }
</managerEventInstance> }
***/
manager_event(EVENT_FLAG_DIALPLAN, "Newexten",
"Channel: %s\r\n"
"Context: %s\r\n"
"Extension: %s\r\n"
"Priority: %d\r\n"
"Application: %s\r\n"
"AppData: %s\r\n"
"Uniqueid: %s\r\n",
ast_channel_name(c), ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), app->name, passdata, ast_channel_uniqueid(c));
return pbx_exec(c, app, passdata); /* 0 on success, -1 on failure */ return pbx_exec(c, app, passdata); /* 0 on success, -1 on failure */
} }
} else if (q.swo) { /* not found here, but in another switch */ } else if (q.swo) { /* not found here, but in another switch */

@ -345,6 +345,8 @@ static int realtime_exec(struct ast_channel *chan, const char *context, const ch
char tmp1[80]; char tmp1[80];
char tmp2[80]; char tmp2[80];
char tmp3[EXT_DATA_SIZE]; char tmp3[EXT_DATA_SIZE];
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
appdata[0] = 0; /* just in case the substitute var func isn't called */ appdata[0] = 0; /* just in case the substitute var func isn't called */
if(!ast_strlen_zero(tmp)) if(!ast_strlen_zero(tmp))
@ -354,16 +356,18 @@ static int realtime_exec(struct ast_channel *chan, const char *context, const ch
term_color(tmp1, app, COLOR_BRCYAN, 0, sizeof(tmp1)), term_color(tmp1, app, COLOR_BRCYAN, 0, sizeof(tmp1)),
term_color(tmp2, ast_channel_name(chan), COLOR_BRMAGENTA, 0, sizeof(tmp2)), term_color(tmp2, ast_channel_name(chan), COLOR_BRMAGENTA, 0, sizeof(tmp2)),
term_color(tmp3, S_OR(appdata, ""), COLOR_BRMAGENTA, 0, sizeof(tmp3))); term_color(tmp3, S_OR(appdata, ""), COLOR_BRMAGENTA, 0, sizeof(tmp3)));
manager_event(EVENT_FLAG_DIALPLAN, "Newexten", snapshot = ast_channel_snapshot_create(chan);
"Channel: %s\r\n" if (snapshot) {
"Context: %s\r\n" /* pbx_exec sets application name and data, but we don't want to log
"Extension: %s\r\n" * every exec. Just update the snapshot here instead.
"Priority: %d\r\n" */
"Application: %s\r\n" ast_string_field_set(snapshot, appl, app);
"AppData: %s\r\n" ast_string_field_set(snapshot, data, !ast_strlen_zero(appdata) ? appdata : "(NULL)");
"Uniqueid: %s\r\n", msg = stasis_message_create(ast_channel_snapshot(), snapshot);
ast_channel_name(chan), ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan), app, !ast_strlen_zero(appdata) ? appdata : "(NULL)", ast_channel_uniqueid(chan)); if (msg) {
stasis_publish(ast_channel_topic(chan), msg);
}
}
res = pbx_exec(chan, a, appdata); res = pbx_exec(chan, a, appdata);
} else } else
ast_log(LOG_NOTICE, "No such application '%s' for extension '%s' in context '%s'\n", app, exten, context); ast_log(LOG_NOTICE, "No such application '%s' for extension '%s' in context '%s'\n", app, exten, context);

@ -1,6 +0,0 @@
{
global:
LINKER_SYMBOL_PREFIXast_json_*;
local:
*;
};

@ -31,7 +31,6 @@
/*** MODULEINFO /*** MODULEINFO
<depend>TEST_FRAMEWORK</depend> <depend>TEST_FRAMEWORK</depend>
<depend>res_json</depend>
<support_level>core</support_level> <support_level>core</support_level>
***/ ***/
@ -1720,5 +1719,4 @@ static int load_module(void)
AST_MODULE_INFO(ASTERISK_GPL_KEY, 0, "JSON testing", AST_MODULE_INFO(ASTERISK_GPL_KEY, 0, "JSON testing",
.load = load_module, .load = load_module,
.unload = unload_module, .unload = unload_module);
.nonoptreq = "res_json");

Loading…
Cancel
Save