mirror of
https://dev.iopsys.eu/bbf/bbfdm.git
synced 2025-12-10 07:44:39 +01:00
Feature #13149: Move event notification mappings to micro-services
This commit is contained in:
parent
6beb5e0e91
commit
36a805cac4
14 changed files with 289 additions and 189 deletions
|
|
@ -1084,7 +1084,7 @@ static int bbfdm_notify_event(struct ubus_context *ctx, struct ubus_object *obj,
|
||||||
return UBUS_STATUS_INVALID_ARGUMENT;
|
return UBUS_STATUS_INVALID_ARGUMENT;
|
||||||
|
|
||||||
INFO("ubus method|%s|, name|%s|", method, obj->name);
|
INFO("ubus method|%s|, name|%s|", method, obj->name);
|
||||||
snprintf(method_name, sizeof(method_name), "%s.%s", u->config.out_name, BBF_EVENT);
|
snprintf(method_name, sizeof(method_name), "%s.%s", DM_STRLEN(u->config.out_root_obj) ? u->config.out_root_obj : u->config.out_name, BBF_EVENT_NAME);
|
||||||
ubus_send_event(ctx, method_name, msg);
|
ubus_send_event(ctx, method_name, msg);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -1172,7 +1172,7 @@ static void broadcast_add_del_event(const char *method, struct list_head *inst,
|
||||||
|
|
||||||
static void update_instances_list(struct list_head *inst)
|
static void update_instances_list(struct list_head *inst)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
struct dmctx bbf_ctx = {
|
struct dmctx bbf_ctx = {
|
||||||
.in_param = ROOT_NODE,
|
.in_param = ROOT_NODE,
|
||||||
.nextlevel = false,
|
.nextlevel = false,
|
||||||
|
|
@ -1737,12 +1737,12 @@ int main(int argc, char **argv)
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
run_schema_updater(&bbfdm_ctx);
|
run_schema_updater(&bbfdm_ctx);
|
||||||
if (is_micro_service == false) { // It's not a micro-service instance
|
|
||||||
err = register_events_to_ubus(&bbfdm_ctx.ubus_ctx, &bbfdm_ctx.event_handlers);
|
|
||||||
if (err != 0)
|
|
||||||
goto exit;
|
|
||||||
|
|
||||||
} else { // It's a micro-service instance
|
err = register_events_to_ubus(&bbfdm_ctx.ubus_ctx, &bbfdm_ctx.event_handlers);
|
||||||
|
if (err != 0)
|
||||||
|
goto exit;
|
||||||
|
|
||||||
|
if (is_micro_service == true) { // It's a micro-service instance
|
||||||
bool is_registred = register_service(&bbfdm_ctx.ubus_ctx);
|
bool is_registred = register_service(&bbfdm_ctx.ubus_ctx);
|
||||||
if (is_registred == false) {
|
if (is_registred == false) {
|
||||||
// register for add event
|
// register for add event
|
||||||
|
|
@ -1757,8 +1757,7 @@ int main(int argc, char **argv)
|
||||||
uloop_run();
|
uloop_run();
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
if (is_micro_service == false) // It's not a micro-service instance
|
free_ubus_event_handler(&bbfdm_ctx.ubus_ctx, &bbfdm_ctx.event_handlers);
|
||||||
free_ubus_event_handler(&bbfdm_ctx.ubus_ctx, &bbfdm_ctx.event_handlers);
|
|
||||||
|
|
||||||
if (ubus_init_done) {
|
if (ubus_init_done) {
|
||||||
uloop_done();
|
uloop_done();
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ struct bbfdm_context {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ev_handler_node {
|
struct ev_handler_node {
|
||||||
|
char *dm_path;
|
||||||
|
char *ev_name;
|
||||||
struct ubus_event_handler *ev_handler;
|
struct ubus_event_handler *ev_handler;
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
#define ROOT_NODE "Device."
|
#define ROOT_NODE "Device."
|
||||||
#define BBF_ADD_EVENT "AddObj"
|
#define BBF_ADD_EVENT "AddObj"
|
||||||
#define BBF_DEL_EVENT "DelObj"
|
#define BBF_DEL_EVENT "DelObj"
|
||||||
#define BBF_EVENT "event"
|
#define BBF_EVENT_NAME "event"
|
||||||
#define BBF_UPDATE_SCHEMA_EVENT "UpdateSchema"
|
#define BBF_UPDATE_SCHEMA_EVENT "UpdateSchema"
|
||||||
|
|
||||||
#define MAX_DM_KEY_LEN 256
|
#define MAX_DM_KEY_LEN 256
|
||||||
|
|
|
||||||
|
|
@ -15,134 +15,21 @@
|
||||||
#include "bbfdmd.h"
|
#include "bbfdmd.h"
|
||||||
#include <libubus.h>
|
#include <libubus.h>
|
||||||
|
|
||||||
static struct event_map_list ev_map_list[] = {
|
static char *get_events_dm_path(struct list_head *ev_list, const char *event)
|
||||||
/* { event name, DM Path, .arguments[] = { event_args, dm_args } } */
|
|
||||||
{ "wifi.dataelements.Associated", "Device.WiFi.DataElements.AssociationEvent.Associated!",
|
|
||||||
.args = {
|
|
||||||
{ "eventTime", "TimeStamp" },
|
|
||||||
{ "wfa-dataelements:AssociationEvent.AssocData.BSSID", "BSSID" },
|
|
||||||
{ "wfa-dataelements:AssociationEvent.AssocData.MACAddress", "MACAddress" },
|
|
||||||
{ "wfa-dataelements:AssociationEvent.AssocData.StatusCode", "StatusCode" },
|
|
||||||
{ "wfa-dataelements:AssociationEvent.AssocData.HTCapabilities", "HTCapabilities" },
|
|
||||||
{ "wfa-dataelements:AssociationEvent.AssocData.VHTCapabilities", "VHTCapabilities" },
|
|
||||||
{ "wfa-dataelements:AssociationEvent.AssocData.HECapabilities", "HECapabilities" },
|
|
||||||
{0}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "wifi.dataelements.Disassociated", "Device.WiFi.DataElements.DisassociationEvent.Disassociated!",
|
|
||||||
.args = {
|
|
||||||
{ "eventTime", "TimeStamp" },
|
|
||||||
{ "wfa-dataelements:DisassociationEvent.DisassocData.BSSID", "BSSID" },
|
|
||||||
{ "wfa-dataelements:DisassociationEvent.DisassocData.MACAddress", "MACAddress" },
|
|
||||||
{ "wfa-dataelements:DisassociationEvent.DisassocData.ReasonCode", "ReasonCode" },
|
|
||||||
{ "wfa-dataelements:DisassociationEvent.DisassocData.BytesSent", "BytesSent" },
|
|
||||||
{ "wfa-dataelements:DisassociationEvent.DisassocData.BytesReceived", "BytesReceived" },
|
|
||||||
{ "wfa-dataelements:DisassociationEvent.DisassocData.PacketsSent", "PacketsSent" },
|
|
||||||
{ "wfa-dataelements:DisassociationEvent.DisassocData.PacketsReceived", "PacketsReceived" },
|
|
||||||
{ "wfa-dataelements:DisassociationEvent.DisassocData.ErrorsSent", "ErrorsSent" },
|
|
||||||
{ "wfa-dataelements:DisassociationEvent.DisassocData.ErrorsReceived", "ErrorsReceived" },
|
|
||||||
{ "wfa-dataelements:DisassociationEvent.DisassocData.RetransCount", "RetransCount" },
|
|
||||||
{0}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static char* get_events_dm_path(const char *event)
|
|
||||||
{
|
{
|
||||||
unsigned int i;
|
struct ev_handler_node *iter = NULL;
|
||||||
|
|
||||||
if (!event) {
|
if (ev_list == NULL || event == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < sizeof(ev_map_list)/sizeof(ev_map_list[0]); i++) {
|
list_for_each_entry(iter, ev_list, list) {
|
||||||
if (strcmp(event, ev_map_list[i].event) == 0)
|
if (iter->ev_name && strcmp(iter->ev_name, event) == 0)
|
||||||
return ev_map_list[i].dm_path;
|
return iter->dm_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct event_args_list* get_events_args(const char *event)
|
|
||||||
{
|
|
||||||
unsigned int i;
|
|
||||||
|
|
||||||
if (!event) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < sizeof(ev_map_list)/sizeof(ev_map_list[0]); i++) {
|
|
||||||
if (strcmp(event, ev_map_list[i].event) == 0)
|
|
||||||
return ev_map_list[i].args;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void serialize_blob_msg(struct blob_attr *msg, char *node, struct list_head *pv_list)
|
|
||||||
{
|
|
||||||
struct blob_attr *attr;
|
|
||||||
size_t rem;
|
|
||||||
|
|
||||||
blobmsg_for_each_attr(attr, msg, rem) {
|
|
||||||
char path[MAX_DM_PATH], value[MAX_DM_VALUE];
|
|
||||||
|
|
||||||
snprintf(path, sizeof(path), "%s%s%s",
|
|
||||||
DM_STRLEN(node) ? node : "",
|
|
||||||
blobmsg_name(attr),
|
|
||||||
(blobmsg_type(attr) == BLOBMSG_TYPE_TABLE && DM_STRLEN(blobmsg_name(attr))) ? "." : "");
|
|
||||||
|
|
||||||
switch (blobmsg_type(attr)) {
|
|
||||||
case BLOBMSG_TYPE_STRING:
|
|
||||||
snprintf(value, MAX_DM_VALUE, "%s", blobmsg_get_string(attr));
|
|
||||||
add_pv_list(path, value, NULL, pv_list);
|
|
||||||
break;
|
|
||||||
case BLOBMSG_TYPE_INT8:
|
|
||||||
snprintf(value, MAX_DM_VALUE, "%d", blobmsg_get_u8(attr));
|
|
||||||
add_pv_list(path, value, NULL, pv_list);
|
|
||||||
break;
|
|
||||||
case BLOBMSG_TYPE_INT16:
|
|
||||||
snprintf(value, MAX_DM_VALUE, "%d", blobmsg_get_u16(attr));
|
|
||||||
add_pv_list(path, value, NULL, pv_list);
|
|
||||||
break;
|
|
||||||
case BLOBMSG_TYPE_INT32:
|
|
||||||
snprintf(value, MAX_DM_VALUE, "%u", blobmsg_get_u32(attr));
|
|
||||||
add_pv_list(path, value, NULL, pv_list);
|
|
||||||
break;
|
|
||||||
case BLOBMSG_TYPE_INT64:
|
|
||||||
snprintf(value, MAX_DM_VALUE, "%"PRIu64"", blobmsg_get_u64(attr));
|
|
||||||
add_pv_list(path, value, NULL, pv_list);
|
|
||||||
break;
|
|
||||||
case BLOBMSG_TYPE_TABLE:
|
|
||||||
serialize_blob_msg(attr, path, pv_list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *get_dm_arg_value(const char *event_arg, struct list_head *pv_list)
|
|
||||||
{
|
|
||||||
struct pvNode *pv = NULL;
|
|
||||||
|
|
||||||
list_for_each_entry(pv, pv_list, list) {
|
|
||||||
if (strcmp(pv->param, event_arg) == 0)
|
|
||||||
return pv->val;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void generate_blob_input(struct blob_buf *b, const char *type, struct list_head *pv_list)
|
|
||||||
{
|
|
||||||
struct event_args_list *args = get_events_args(type);
|
|
||||||
if (args == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (int i = 0; args[i].event_arg; i++) {
|
|
||||||
char *dm_arg = get_dm_arg_value(args[i].event_arg, pv_list);
|
|
||||||
blobmsg_add_string(b, args[i].dm_arg, dm_arg ? dm_arg : "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void bbfdm_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev,
|
static void bbfdm_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev,
|
||||||
const char *type, struct blob_attr *msg)
|
const char *type, struct blob_attr *msg)
|
||||||
{
|
{
|
||||||
|
|
@ -158,19 +45,31 @@ static void bbfdm_event_handler(struct ubus_context *ctx, struct ubus_event_hand
|
||||||
if (!msg || !type)
|
if (!msg || !type)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
char *dm_path = get_events_dm_path(type);
|
char *dm_path = get_events_dm_path(&u->event_handlers, type);
|
||||||
if (dm_path == NULL)
|
if (dm_path == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// restart instance timer
|
struct dmctx bbf_ctx = {
|
||||||
register_periodic_timers(ctx);
|
.in_param = dm_path,
|
||||||
|
.in_value = blobmsg_format_json(msg, true),
|
||||||
|
.nextlevel = false,
|
||||||
|
.iscommand = false,
|
||||||
|
.isevent = true,
|
||||||
|
.isinfo = false,
|
||||||
|
.disable_mservice_browse = true,
|
||||||
|
.instance_mode = INSTANCE_MODE_NUMBER,
|
||||||
|
.dm_type = BBFDM_USP
|
||||||
|
};
|
||||||
|
|
||||||
LIST_HEAD(pv_list);
|
bbf_init(&bbf_ctx);
|
||||||
|
|
||||||
serialize_blob_msg(msg, "", &pv_list);
|
int ret = bbfdm_cmd_exec(&bbf_ctx, BBF_EVENT);
|
||||||
|
if (ret)
|
||||||
|
goto end;
|
||||||
|
|
||||||
struct blob_buf b, bb;
|
struct dm_parameter *param = NULL;
|
||||||
char method_name[40] = {0};
|
struct blob_buf b = {0}, bb = {0};
|
||||||
|
char method_name[64] = {0};
|
||||||
|
|
||||||
memset(&b, 0, sizeof(struct blob_buf));
|
memset(&b, 0, sizeof(struct blob_buf));
|
||||||
memset(&bb, 0, sizeof(struct blob_buf));
|
memset(&bb, 0, sizeof(struct blob_buf));
|
||||||
|
|
@ -178,20 +77,26 @@ static void bbfdm_event_handler(struct ubus_context *ctx, struct ubus_event_hand
|
||||||
blob_buf_init(&b, 0);
|
blob_buf_init(&b, 0);
|
||||||
blob_buf_init(&bb, 0);
|
blob_buf_init(&bb, 0);
|
||||||
|
|
||||||
snprintf(method_name, sizeof(method_name), "%s.%s", u->config.out_name, BBF_EVENT);
|
list_for_each_entry(param, &bbf_ctx.list_parameter, list) {
|
||||||
|
blobmsg_add_string(&bb, param->name, param->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(method_name, sizeof(method_name), "%s.%s", DM_STRLEN(u->config.out_root_obj) ? u->config.out_root_obj : u->config.out_name, BBF_EVENT_NAME);
|
||||||
|
|
||||||
blobmsg_add_string(&b, "name", dm_path);
|
blobmsg_add_string(&b, "name", dm_path);
|
||||||
generate_blob_input(&bb, type, &pv_list);
|
|
||||||
blobmsg_add_field(&b, BLOBMSG_TYPE_TABLE, "input", blob_data(bb.head), blob_len(bb.head));
|
blobmsg_add_field(&b, BLOBMSG_TYPE_TABLE, "input", blob_data(bb.head), blob_len(bb.head));
|
||||||
|
|
||||||
ubus_send_event(ctx, method_name, b.head);
|
ubus_send_event(ctx, method_name, b.head);
|
||||||
DEBUG("Event[%s], for [%s] sent", method_name, dm_path);
|
DEBUG("Event[%s], for [%s] sent", method_name, dm_path);
|
||||||
|
|
||||||
blob_buf_free(&bb);
|
blob_buf_free(&bb);
|
||||||
blob_buf_free(&b);
|
blob_buf_free(&b);
|
||||||
free_pv_list(&pv_list);
|
|
||||||
|
end:
|
||||||
|
bbf_cleanup(&bbf_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_ubus_event_handler(struct ubus_event_handler *ev, struct list_head *ev_list)
|
static void add_ubus_event_handler(struct ubus_event_handler *ev, const char *ev_name, const char *dm_path, struct list_head *ev_list)
|
||||||
{
|
{
|
||||||
if (ev == NULL || ev_list == NULL)
|
if (ev == NULL || ev_list == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
@ -204,11 +109,68 @@ static void add_ubus_event_handler(struct ubus_event_handler *ev, struct list_he
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node->ev_name = ev_name ? strdup(ev_name) : NULL;
|
||||||
|
node->dm_path = dm_path ? strdup(dm_path) : NULL;
|
||||||
node->ev_handler = ev;
|
node->ev_handler = ev;
|
||||||
INIT_LIST_HEAD(&node->list);
|
INIT_LIST_HEAD(&node->list);
|
||||||
list_add_tail(&node->list, ev_list);
|
list_add_tail(&node->list, ev_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int register_events_to_ubus(struct ubus_context *ctx, struct list_head *ev_list)
|
||||||
|
{
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
if (ctx == NULL || ev_list == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
struct dmctx bbf_ctx = {
|
||||||
|
.in_param = ROOT_NODE,
|
||||||
|
.nextlevel = false,
|
||||||
|
.iscommand = false,
|
||||||
|
.isevent = true,
|
||||||
|
.isinfo = false,
|
||||||
|
.disable_mservice_browse = true,
|
||||||
|
.instance_mode = INSTANCE_MODE_NUMBER,
|
||||||
|
.dm_type = BBFDM_USP
|
||||||
|
};
|
||||||
|
|
||||||
|
bbf_init(&bbf_ctx);
|
||||||
|
|
||||||
|
if (0 == bbfdm_cmd_exec(&bbf_ctx, BBF_SCHEMA)) {
|
||||||
|
struct dm_parameter *param;
|
||||||
|
|
||||||
|
list_for_each_entry(param, &bbf_ctx.list_parameter, list) {
|
||||||
|
event_args *event = (event_args *)param->data;
|
||||||
|
|
||||||
|
if (!param->name || !event || !event->name || !strlen(event->name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
struct ubus_event_handler *ev = (struct ubus_event_handler *)malloc(sizeof(struct ubus_event_handler));
|
||||||
|
if (!ev) {
|
||||||
|
ERR("Out of memory!");
|
||||||
|
err = -1;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(ev, 0, sizeof(struct ubus_event_handler));
|
||||||
|
ev->cb = bbfdm_event_handler;
|
||||||
|
|
||||||
|
if (0 != ubus_register_event_handler(ctx, ev, event->name)) {
|
||||||
|
ERR("Failed to register: %s", event->name);
|
||||||
|
err = -1;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_ubus_event_handler(ev, event->name, param->name, ev_list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
bbf_cleanup(&bbf_ctx);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
void free_ubus_event_handler(struct ubus_context *ctx, struct list_head *ev_list)
|
void free_ubus_event_handler(struct ubus_context *ctx, struct list_head *ev_list)
|
||||||
{
|
{
|
||||||
struct ev_handler_node *iter = NULL, *node = NULL;
|
struct ev_handler_node *iter = NULL, *node = NULL;
|
||||||
|
|
@ -222,39 +184,13 @@ void free_ubus_event_handler(struct ubus_context *ctx, struct list_head *ev_list
|
||||||
free(iter->ev_handler);
|
free(iter->ev_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (iter->dm_path)
|
||||||
|
free(iter->dm_path);
|
||||||
|
|
||||||
|
if (iter->ev_name)
|
||||||
|
free(iter->ev_name);
|
||||||
|
|
||||||
list_del(&iter->list);
|
list_del(&iter->list);
|
||||||
free(iter);
|
free(iter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int register_events_to_ubus(struct ubus_context *ctx, struct list_head *ev_list)
|
|
||||||
{
|
|
||||||
unsigned int i;
|
|
||||||
|
|
||||||
if (ctx == NULL || ev_list == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
for (i = 0; i < sizeof(ev_map_list)/sizeof(ev_map_list[0]); i++) {
|
|
||||||
if (ev_map_list[i].event == NULL) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ubus_event_handler *ev = (struct ubus_event_handler *)malloc(sizeof(struct ubus_event_handler));
|
|
||||||
if (!ev) {
|
|
||||||
ERR("Out of memory!");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(ev, 0, sizeof(struct ubus_event_handler));
|
|
||||||
ev->cb = bbfdm_event_handler;
|
|
||||||
|
|
||||||
if (0 != ubus_register_event_handler(ctx, ev, ev_map_list[i].event)) {
|
|
||||||
ERR("Failed to register: %s", ev_map_list[i].event);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
add_ubus_event_handler(ev, ev_list);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,7 @@
|
||||||
#include "bbfdmd.h"
|
#include "bbfdmd.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
struct event_args_list {
|
|
||||||
char *event_arg;
|
|
||||||
char *dm_arg;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct event_map_list {
|
|
||||||
char *event;
|
|
||||||
char *dm_path;
|
|
||||||
struct event_args_list args[16];
|
|
||||||
};
|
|
||||||
|
|
||||||
void free_ubus_event_handler(struct ubus_context *ctx, struct list_head *ev_list);
|
|
||||||
int register_events_to_ubus(struct ubus_context *ctx, struct list_head *ev_list);
|
int register_events_to_ubus(struct ubus_context *ctx, struct list_head *ev_list);
|
||||||
|
void free_ubus_event_handler(struct ubus_context *ctx, struct list_head *ev_list);
|
||||||
|
|
||||||
#endif /* EVENT_H */
|
#endif /* EVENT_H */
|
||||||
|
|
|
||||||
63
docs/guide/bbfdmd_event.md
Normal file
63
docs/guide/bbfdmd_event.md
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Monitoring Events in the Data Model
|
||||||
|
|
||||||
|
`bbfdmd` now supports monitoring events directly from the Data Model, eliminating the need for internal registration and handling within `bbfdmd`.
|
||||||
|
|
||||||
|
# How to add an new event
|
||||||
|
|
||||||
|
- Insert the event into the required `DMLEAF` table
|
||||||
|
|
||||||
|
- Ensure that `leaf_type` is defined as `DMT_EVENT` and `bbfdm_type` as `BBFDM_USP`
|
||||||
|
|
||||||
|
- Implement the get/set event API
|
||||||
|
|
||||||
|
# How it works
|
||||||
|
|
||||||
|
Upon starting `bbfdmd`, it calls `bbf_entry_method` API with `BBF_SCHEMA` method to retrieve all events supported by Data Model. Subsequently, it attempts to register an event handler for each event by using the event name argument defined in each event leaf and then listens for that event name.
|
||||||
|
|
||||||
|
When the event name is triggered, `bbfdmd` calls `bbf_entry_method` API with `BBF_EVENT` method to perform the event operation. And finally, it sends `bbfdm.event` ubus event with the required input information obtained from the returned event operation.
|
||||||
|
|
||||||
|
# Event Example
|
||||||
|
|
||||||
|
Below is an example of `Device.WiFi.DataElements.AssociationEvent.Associated!` event implementation:
|
||||||
|
|
||||||
|
static event_args wifidataelementsassociationevent_associated_args = {
|
||||||
|
.name = "wifi.dataelements.Associated",
|
||||||
|
.param = (const char *[]) {
|
||||||
|
"type",
|
||||||
|
"version",
|
||||||
|
"protocols",
|
||||||
|
"BSSID",
|
||||||
|
"MACAddress",
|
||||||
|
"StatusCode",
|
||||||
|
"HTCapabilities",
|
||||||
|
"VHTCapabilities",
|
||||||
|
"HECapabilities",
|
||||||
|
"TimeStamp",
|
||||||
|
NULL
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static int get_event_args_WiFiDataElementsAssociationEvent_Associated(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
|
||||||
|
{
|
||||||
|
*value = (char *)&wifidataelementsassociationevent_associated_args;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int event_WiFiDataElementsAssociationEvent_Associated(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
|
||||||
|
{
|
||||||
|
char *event_time = dmjson_get_value((json_object *)value, 1, "eventTime");
|
||||||
|
char *bssid = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:AssociationEvent.AssocData", "DisassocData", "BSSID");
|
||||||
|
char *mac_addr = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:AssociationEvent.AssocData", "DisassocData", "MACAddress");
|
||||||
|
|
||||||
|
add_list_parameter(ctx, dmstrdup("TimeStamp"), dmstrdup(event_time), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("BSSID"), dmstrdup(bssid), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("MACAddress"), dmstrdup(mac_addr), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DMLEAF tWiFiDataElementsAssociationEventParams[] = {
|
||||||
|
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type*/
|
||||||
|
{"Associated!", &DMREAD, DMT_EVENT, get_event_args_WiFiDataElementsAssociationEvent_Associated, event_WiFiDataElementsAssociationEvent_Associated, BBFDM_USP},
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
@ -237,6 +237,7 @@ typedef struct {
|
||||||
} operation_args;
|
} operation_args;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
const char **param;
|
const char **param;
|
||||||
} event_args;
|
} event_args;
|
||||||
|
|
||||||
|
|
@ -272,6 +273,7 @@ enum {
|
||||||
BBF_ADD_OBJECT,
|
BBF_ADD_OBJECT,
|
||||||
BBF_DEL_OBJECT,
|
BBF_DEL_OBJECT,
|
||||||
BBF_OPERATE,
|
BBF_OPERATE,
|
||||||
|
BBF_EVENT,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum usp_fault_code_enum {
|
enum usp_fault_code_enum {
|
||||||
|
|
|
||||||
|
|
@ -423,9 +423,8 @@ static void dm_browse_service(struct dmctx *dmctx, DMNODE *parent_node, DMOBJ *e
|
||||||
node.parent = parent_node;
|
node.parent = parent_node;
|
||||||
node.is_ubus_service = true;
|
node.is_ubus_service = true;
|
||||||
|
|
||||||
if (dmctx->disable_mservice_browse == true) {
|
if (dmctx->disable_mservice_browse == true)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
dmasprintf(&(node.current_object), "%s%s.", parent_obj, entryobj->obj);
|
dmasprintf(&(node.current_object), "%s%s.", parent_obj, entryobj->obj);
|
||||||
|
|
||||||
|
|
@ -2650,7 +2649,7 @@ static int mparam_operate(DMPARAM_ARGS)
|
||||||
return USP_FAULT_COMMAND_FAILURE;
|
return USP_FAULT_COMMAND_FAILURE;
|
||||||
|
|
||||||
json_object *j_input = (dmctx->in_value) ? json_tokener_parse(dmctx->in_value) : NULL;
|
json_object *j_input = (dmctx->in_value) ? json_tokener_parse(dmctx->in_value) : NULL;
|
||||||
int fault = (leaf->setvalue)(full_param, dmctx, data, instance, (char *)j_input, VALUESET);
|
int fault = (leaf->setvalue)(full_param, dmctx, data, instance, (char *)j_input, 0);
|
||||||
json_object_put(j_input);
|
json_object_put(j_input);
|
||||||
|
|
||||||
return fault;
|
return fault;
|
||||||
|
|
@ -2678,3 +2677,54 @@ int dm_entry_operate(struct dmctx *dmctx)
|
||||||
|
|
||||||
return (dmctx->stop) ? err : USP_FAULT_INVALID_PATH;
|
return (dmctx->stop) ? err : USP_FAULT_INVALID_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* **************
|
||||||
|
* Event
|
||||||
|
* **************/
|
||||||
|
static int mobj_event(DMOBJECT_ARGS)
|
||||||
|
{
|
||||||
|
return USP_FAULT_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mparam_event(DMPARAM_ARGS)
|
||||||
|
{
|
||||||
|
char full_param[MAX_DM_PATH];
|
||||||
|
|
||||||
|
snprintf(full_param, MAX_DM_PATH, "%s%s", node->current_object, leaf->parameter);
|
||||||
|
|
||||||
|
if (DM_STRCMP(full_param, dmctx->in_param) != 0)
|
||||||
|
return USP_FAULT_INVALID_PATH;
|
||||||
|
|
||||||
|
dmctx->stop = 1;
|
||||||
|
|
||||||
|
if (!leaf->setvalue)
|
||||||
|
return USP_FAULT_INTERNAL_ERROR;
|
||||||
|
|
||||||
|
json_object *j_input = (dmctx->in_value) ? json_tokener_parse(dmctx->in_value) : NULL;
|
||||||
|
int fault = (leaf->setvalue)(full_param, dmctx, data, instance, (char *)j_input, 0);
|
||||||
|
json_object_put(j_input);
|
||||||
|
|
||||||
|
return fault;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dm_entry_event(struct dmctx *dmctx)
|
||||||
|
{
|
||||||
|
DMOBJ *root = dmctx->dm_entryobj;
|
||||||
|
DMNODE node = { .current_object = "" };
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
if (dmctx->in_param == NULL || dmctx->in_param[0] == '\0' || (*(dmctx->in_param + DM_STRLEN(dmctx->in_param) - 1) != '!'))
|
||||||
|
return USP_FAULT_INVALID_PATH;
|
||||||
|
|
||||||
|
dmctx->isevent = 1;
|
||||||
|
dmctx->inparam_isparam = 1;
|
||||||
|
dmctx->stop = 0;
|
||||||
|
dmctx->checkobj = plugin_obj_match;
|
||||||
|
dmctx->checkleaf = plugin_leaf_match;
|
||||||
|
dmctx->method_obj = mobj_event;
|
||||||
|
dmctx->method_param = mparam_event;
|
||||||
|
|
||||||
|
err = dm_browse(dmctx, &node, root, NULL, NULL);
|
||||||
|
|
||||||
|
return (dmctx->stop) ? err : USP_FAULT_INVALID_PATH;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ int dm_entry_delete_object(struct dmctx *dmctx);
|
||||||
int dm_entry_set_value(struct dmctx *dmctx);
|
int dm_entry_set_value(struct dmctx *dmctx);
|
||||||
int dm_entry_object_exists(struct dmctx *dmctx);
|
int dm_entry_object_exists(struct dmctx *dmctx);
|
||||||
int dm_entry_operate(struct dmctx *dmctx);
|
int dm_entry_operate(struct dmctx *dmctx);
|
||||||
|
int dm_entry_event(struct dmctx *dmctx);
|
||||||
int dm_entry_get_reference_param(struct dmctx *dmctx);
|
int dm_entry_get_reference_param(struct dmctx *dmctx);
|
||||||
int dm_entry_get_reference_value(struct dmctx *dmctx);
|
int dm_entry_get_reference_value(struct dmctx *dmctx);
|
||||||
int dm_entry_get_linker(struct dmctx *dmctx);
|
int dm_entry_get_linker(struct dmctx *dmctx);
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,9 @@ int bbf_entry_method(struct dmctx *ctx, int cmd)
|
||||||
case BBF_OPERATE:
|
case BBF_OPERATE:
|
||||||
fault = dm_entry_operate(ctx);
|
fault = dm_entry_operate(ctx);
|
||||||
break;
|
break;
|
||||||
|
case BBF_EVENT:
|
||||||
|
fault = dm_entry_event(ctx);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
dmuci_save();
|
dmuci_save();
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ static operation_args empty_cmd = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static event_args empty_event = {
|
static event_args empty_event = {
|
||||||
|
.name = (const char *)NULL,
|
||||||
.param = (const char**)NULL,
|
.param = (const char**)NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3512,6 +3512,7 @@ static int operate_WiFiDataElementsNetworkDeviceRadio_WiFiRestart(char *refparam
|
||||||
* EVENTS
|
* EVENTS
|
||||||
*************************************************************/
|
*************************************************************/
|
||||||
static event_args wifidataelementsassociationevent_associated_args = {
|
static event_args wifidataelementsassociationevent_associated_args = {
|
||||||
|
.name = "wifi.dataelements.Associated",
|
||||||
.param = (const char *[]) {
|
.param = (const char *[]) {
|
||||||
"type",
|
"type",
|
||||||
"version",
|
"version",
|
||||||
|
|
@ -3533,7 +3534,29 @@ static int get_event_args_WiFiDataElementsAssociationEvent_Associated(char *refp
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int event_WiFiDataElementsAssociationEvent_Associated(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
|
||||||
|
{
|
||||||
|
char *event_time = dmjson_get_value((json_object *)value, 1, "eventTime");
|
||||||
|
char *bssid = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:AssociationEvent.AssocData", "DisassocData", "BSSID");
|
||||||
|
char *mac_addr = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:AssociationEvent.AssocData", "DisassocData", "MACAddress");
|
||||||
|
char *status_code = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:AssociationEvent.AssocData", "DisassocData", "StatusCode");
|
||||||
|
char *ht_cap = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:AssociationEvent.AssocData", "DisassocData", "HTCapabilities");
|
||||||
|
char *vht_cap = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:AssociationEvent.AssocData", "DisassocData", "VHTCapabilities");
|
||||||
|
char *he_cap = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:AssociationEvent.AssocData", "DisassocData", "HECapabilities");
|
||||||
|
|
||||||
|
add_list_parameter(ctx, dmstrdup("TimeStamp"), dmstrdup(event_time), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("BSSID"), dmstrdup(bssid), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("MACAddress"), dmstrdup(mac_addr), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("StatusCode"), dmstrdup(status_code), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("HTCapabilities"), dmstrdup(ht_cap), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("VHTCapabilities"), dmstrdup(vht_cap), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("HECapabilities"), dmstrdup(he_cap), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static event_args wifidataelementsdisassociationevent_disassociated_args = {
|
static event_args wifidataelementsdisassociationevent_disassociated_args = {
|
||||||
|
.name = "wifi.dataelements.Disassociated",
|
||||||
.param = (const char *[]) {
|
.param = (const char *[]) {
|
||||||
"type",
|
"type",
|
||||||
"version",
|
"version",
|
||||||
|
|
@ -3559,6 +3582,35 @@ static int get_event_args_WiFiDataElementsDisassociationEvent_Disassociated(char
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int event_WiFiDataElementsDisassociationEvent_Disassociated(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
|
||||||
|
{
|
||||||
|
char *event_time = dmjson_get_value((json_object *)value, 1, "eventTime");
|
||||||
|
char *bssid = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:DisassociationEvent", "DisassocData", "BSSID");
|
||||||
|
char *mac_addr = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:DisassociationEvent", "DisassocData", "MACAddress");
|
||||||
|
char *reason_code = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:DisassociationEvent", "DisassocData", "ReasonCode");
|
||||||
|
char *bytes_sent = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:DisassociationEvent", "DisassocData", "BytesSent");
|
||||||
|
char *bytes_received = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:DisassociationEvent", "DisassocData", "BytesReceived");
|
||||||
|
char *packet_sent = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:DisassociationEvent", "DisassocData", "PacketsSent");
|
||||||
|
char *packet_received = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:DisassociationEvent", "DisassocData", "PacketsReceived");
|
||||||
|
char *errors_sent = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:DisassociationEvent", "DisassocData", "ErrorsSent");
|
||||||
|
char *errors_received = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:DisassociationEvent", "DisassocData", "ErrorsReceived");
|
||||||
|
char *retrans_count = dmjson_get_value((json_object *)value, 3, "wfa-dataelements:DisassociationEvent", "DisassocData", "RetransCount");
|
||||||
|
|
||||||
|
add_list_parameter(ctx, dmstrdup("TimeStamp"), dmstrdup(event_time), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("BSSID"), dmstrdup(bssid), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("MACAddress"), dmstrdup(mac_addr), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("ReasonCode"), dmstrdup(reason_code), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("BytesSent"), dmstrdup(bytes_sent), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("BytesReceived"), dmstrdup(bytes_received), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("PacketsSent"), dmstrdup(packet_sent), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("PacketsReceived"), dmstrdup(packet_received), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("ErrorsSent"), dmstrdup(errors_sent), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("ErrorsReceived"), dmstrdup(errors_received), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
add_list_parameter(ctx, dmstrdup("RetransCount"), dmstrdup(retrans_count), DMT_TYPE[DMT_STRING], NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************
|
/**********************************************************************************************************************************
|
||||||
* OBJ & LEAF DEFINITION
|
* OBJ & LEAF DEFINITION
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
|
|
@ -4389,7 +4441,7 @@ DMOBJ tWiFiDataElementsAssociationEventObj[] = {
|
||||||
DMLEAF tWiFiDataElementsAssociationEventParams[] = {
|
DMLEAF tWiFiDataElementsAssociationEventParams[] = {
|
||||||
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
|
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
|
||||||
{"AssociationEventDataNumberOfEntries", &DMREAD, DMT_UNINT, get_WiFiDataElementsAssociationEvent_AssociationEventDataNumberOfEntries, NULL, BBFDM_BOTH},
|
{"AssociationEventDataNumberOfEntries", &DMREAD, DMT_UNINT, get_WiFiDataElementsAssociationEvent_AssociationEventDataNumberOfEntries, NULL, BBFDM_BOTH},
|
||||||
{"Associated!", &DMREAD, DMT_EVENT, get_event_args_WiFiDataElementsAssociationEvent_Associated, NULL, BBFDM_USP},
|
{"Associated!", &DMREAD, DMT_EVENT, get_event_args_WiFiDataElementsAssociationEvent_Associated, event_WiFiDataElementsAssociationEvent_Associated, BBFDM_USP},
|
||||||
{0}
|
{0}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -4450,7 +4502,7 @@ DMOBJ tWiFiDataElementsDisassociationEventObj[] = {
|
||||||
DMLEAF tWiFiDataElementsDisassociationEventParams[] = {
|
DMLEAF tWiFiDataElementsDisassociationEventParams[] = {
|
||||||
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
|
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
|
||||||
{"DisassociationEventDataNumberOfEntries", &DMREAD, DMT_UNINT, get_WiFiDataElementsDisassociationEvent_DisassociationEventDataNumberOfEntries, NULL, BBFDM_BOTH},
|
{"DisassociationEventDataNumberOfEntries", &DMREAD, DMT_UNINT, get_WiFiDataElementsDisassociationEvent_DisassociationEventDataNumberOfEntries, NULL, BBFDM_BOTH},
|
||||||
{"Disassociated!", &DMREAD, DMT_EVENT, get_event_args_WiFiDataElementsDisassociationEvent_Disassociated, NULL, BBFDM_USP},
|
{"Disassociated!", &DMREAD, DMT_EVENT, get_event_args_WiFiDataElementsDisassociationEvent_Disassociated, event_WiFiDataElementsDisassociationEvent_Disassociated, BBFDM_USP},
|
||||||
{0}
|
{0}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,7 @@ static int operate_DeviceXIOPSYSEUPingTEST_Run(char *refparam, struct dmctx *ctx
|
||||||
* EVENTS
|
* EVENTS
|
||||||
*************************************************************/
|
*************************************************************/
|
||||||
static event_args boot_event_args = {
|
static event_args boot_event_args = {
|
||||||
|
.name = "",
|
||||||
.param = (const char *[]) {
|
.param = (const char *[]) {
|
||||||
"CommandKey",
|
"CommandKey",
|
||||||
"Cause",
|
"Cause",
|
||||||
|
|
|
||||||
|
|
@ -509,6 +509,7 @@ def cprintEvent(geteventargs, param_args, struct_name):
|
||||||
############################## OPERATE ARGUMENTS ########################################
|
############################## OPERATE ARGUMENTS ########################################
|
||||||
print("static event_args %s = {" % struct_name, file=fp)
|
print("static event_args %s = {" % struct_name, file=fp)
|
||||||
if isinstance(param_args, dict):
|
if isinstance(param_args, dict):
|
||||||
|
print(" .name = \"\"", file=fp)
|
||||||
print(" .param = (const char *[]) {", file=fp)
|
print(" .param = (const char *[]) {", file=fp)
|
||||||
for obj, _val in param_args.items():
|
for obj, _val in param_args.items():
|
||||||
print(" \"%s\"," % obj, file=fp)
|
print(" \"%s\"," % obj, file=fp)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue