Add channel events for res_stasis apps

This change adds a framework in res_stasis for handling events from
channel topics. JSON event generation and validation code is created
from event documentation in rest-api/api-docs/events.json to assist in
JSON event generation, ensure consistency, and ensure that accurate
documentation is available for ALL events that are received by
res_stasis applications.

The userevent application has been refactored along with the code that
handles userevent channel blob events to pass the headers as key/value
pairs in the JSON blob. As a side-effect, app_userevent now handles
duplicate keys by overwriting the previous value.

Review: https://reviewboard.asterisk.org/r/2428/
(closes issue ASTERISK-21180)
Patch-By: Kinsey Moore <kmoore@digium.com>


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@388275 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Kinsey Moore
2013-05-10 13:13:06 +00:00
parent 2cfedc12ad
commit 7ce05bfb9b
18 changed files with 1593 additions and 292 deletions

View File

@@ -107,7 +107,6 @@ class PathSegment(Stringify):
"""
return len(self.__children)
class AsteriskProcessor(SwaggerPostProcessor):
"""A SwaggerPostProcessor which adds fields needed to generate Asterisk
RESTful HTTP binding code.
@@ -145,6 +144,18 @@ class AsteriskProcessor(SwaggerPostProcessor):
segment = resource_api.root_path.get_child(api.path.split('/'))
for operation in api.operations:
segment.operations.append(operation)
resource_api.api_declaration.has_events = False
for model in resource_api.api_declaration.models:
if model.id == "Event":
resource_api.api_declaration.has_events = True
break
if resource_api.api_declaration.has_events:
resource_api.api_declaration.events = \
[self.process_model(model, context) for model in \
resource_api.api_declaration.models if model.id != "Event"]
else:
resource_api.api_declaration.events = []
# Since every API path should start with /[resource], root should
# have exactly one child.
if resource_api.root_path.num_children() != 1:
@@ -177,3 +188,43 @@ class AsteriskProcessor(SwaggerPostProcessor):
parameter.c_space = ''
else:
parameter.c_space = ' '
def process_model(self, model, context):
model.c_id = snakify(model.id)
model.channel = False
model.channel_desc = ""
model.bridge = False
model.bridge_desc = ""
model.properties = [self.process_property(model, prop, context) for prop in model.properties]
model.properties = [prop for prop in model.properties if prop]
model.has_properties = (len(model.properties) != 0)
return model
def process_property(self, model, prop, context):
# process channel separately since it will be pulled out
if prop.name == 'channel' and prop.type == 'Channel':
model.channel = True
model.channel_desc = prop.description or ""
return None
# process bridge separately since it will be pulled out
if prop.name == 'bridge' and prop.type == 'Bridge':
model.bridge = True
model.bridge_desc = prop.description or ""
return None
prop.c_name = snakify(prop.name)
if prop.type in self.type_mapping:
prop.c_type = self.type_mapping[prop.type]
prop.c_convert = self.convert_mapping[prop.c_type]
else:
prop.c_type = "Property type %s not mappable to a C type" % (prop.type)
prop.c_convert = "Property type %s not mappable to a C conversion" % (prop.type)
#raise SwaggerError(
# "Invalid property type %s" % prop.type, context)
# You shouldn't put a space between 'char *' and the variable
if prop.c_type.endswith('*'):
prop.c_space = ''
else:
prop.c_space = ' '
return prop

View File

@@ -0,0 +1,10 @@
struct ast_json *stasis_json_event_{{c_id}}_create(
{{#bridge}}
struct ast_bridge_snapshot *bridge_snapshot{{#channel}},{{/channel}}{{^channel}}{{#has_properties}},{{/has_properties}}{{/channel}}
{{/bridge}}
{{#channel}}
struct ast_channel_snapshot *channel_snapshot{{#has_properties}},{{/has_properties}}
{{/channel}}
{{#has_properties}}
struct ast_json *blob
{{/has_properties}}

View File

@@ -47,6 +47,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/module.h"
#include "stasis_http/resource_{{name}}.h"
{{#has_events}}
#include "asterisk/stasis_channels.h"
{{/has_events}}
{{#apis}}
{{#operations}}
@@ -96,6 +99,89 @@ static void stasis_http_{{c_nickname}}_cb(
{{> rest_handler}}
{{/root_path}}
{{#has_events}}
{{#events}}
{{> event_function_decl}}
)
{
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
{{#has_properties}}
struct ast_json *validator;
{{/has_properties}}
{{#channel}}
int ret;
{{/channel}}
{{#bridge}}
{{^channel}}
int ret;
{{/channel}}
{{/bridge}}
{{#channel}}
ast_assert(channel_snapshot != NULL);
{{/channel}}
{{#bridge}}
ast_assert(bridge_snapshot != NULL);
{{/bridge}}
{{#has_properties}}
ast_assert(blob != NULL);
{{#channel}}
ast_assert(ast_json_object_get(blob, "channel") == NULL);
{{/channel}}
{{#bridge}}
ast_assert(ast_json_object_get(blob, "bridge") == NULL);
{{/bridge}}
ast_assert(ast_json_object_get(blob, "type") == NULL);
{{#properties}}
validator = ast_json_object_get(blob, "{{name}}");
if (validator) {
/* do validation? XXX */
{{#required}}
} else {
/* fail message generation if the required parameter doesn't exist */
return NULL;
{{/required}}
}
{{/properties}}
event = ast_json_deep_copy(blob);
{{/has_properties}}
{{^has_properties}}
event = ast_json_object_create();
{{/has_properties}}
if (!event) {
return NULL;
}
{{#channel}}
ret = ast_json_object_set(event,
"channel", ast_channel_snapshot_to_json(channel_snapshot));
if (ret) {
return NULL;
}
{{/channel}}
{{#bridge}}
ret = ast_json_object_set(event,
"bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
if (ret) {
return NULL;
}
{{/bridge}}
message = ast_json_pack("{s: o}", "{{c_id}}", ast_json_ref(event));
if (!message) {
return NULL;
}
return ast_json_ref(message);
}
{{/events}}
{{/has_events}}
static int load_module(void)
{
return stasis_http_add_handler(&{{root_full_name}});

View File

@@ -64,16 +64,48 @@ void stasis_http_{{c_nickname}}(struct ast_variable *headers, struct ast_{{c_nic
{{/operations}}
{{/apis}}
{{#has_events}}
struct ast_channel_snapshot;
struct ast_bridge_snapshot;
{{#events}}
/*!
* \brief {{description}}
{{#notes}}
*
* {{{notes}}}
{{/notes}}
*
{{#channel}}
* \param channel {{#channel_desc}}{{channel_desc}}{{/channel_desc}}{{^channel_desc}}The channel to be used to generate this event{{/channel_desc}}
{{/channel}}
{{#bridge}}
* \param bridge {{#bridge_desc}}{{bridge_desc}}{{/bridge_desc}}{{^bridge_desc}}The bridge to be used to generate this event{{/bridge_desc}}
{{/bridge}}
{{#has_properties}}
* \param blob JSON blob containing the following parameters:
{{/has_properties}}
{{#properties}}
* - {{name}}: {{type}} {{#description}}- {{description}}{{/description}}{{#required}} (required){{/required}}
{{/properties}}
*
* \retval NULL on error
* \retval JSON (ast_json) describing the event
*/
{{> event_function_decl}}
);
{{/events}}
{{/has_events}}
/*
* JSON models
*
{{#models}}
* {{id}}
{{#properties}}
* - {{name}}: {{type}} {{#required}}(required){{/required}}
* - {{name}}: {{type}}{{#required}} (required){{/required}}
{{/properties}}
{{/models}}
*/
{{/models}} */
#endif /* _ASTERISK_RESOURCE_{{name_caps}}_H */
{{/api_declaration}}

View File

@@ -295,13 +295,17 @@ class Model(Stringify):
def __init__(self):
self.id = None
self.notes = None
self.description = None
self.properties = None
def load(self, id, model_json, processor, context):
context = add_context(context, model_json, 'id')
# This arrangement is required by the Swagger API spec
self.id = model_json.get('id')
if id != self.id:
raise SwaggerError("Model id doesn't match name", c)
self.description = model_json.get('description')
props = model_json.get('properties').items() or []
self.properties = [
Property(k).load(j, processor, context) for (k, j) in props]