From 2e6205389e8643fb6d33cd39626e9c3beb527051 Mon Sep 17 00:00:00 2001 From: "xiang.zhou" Date: Fri, 14 Aug 2026 15:41:21 +0800 Subject: [PATCH] restful: support querying nodes by multiple plugins --- docs/api/cn/http.md | 2 +- docs/api/english/http.md | 2 +- include/neuron/define.h | 1 + include/neuron/msg.h | 3 ++- plugins/restful/adapter_handle.c | 45 +++++++++++++++++++++++++++----- src/core/manager.c | 16 ++++++++---- src/core/manager_internal.c | 10 +++---- src/core/manager_internal.h | 3 ++- src/core/node_manager.c | 18 ++++++++++--- src/core/node_manager.h | 3 ++- tests/ft/neuron/api.py | 12 +++++---- tests/ft/node/test_node.py | 27 +++++++++++++++++++ 12 files changed, 111 insertions(+), 31 deletions(-) diff --git a/docs/api/cn/http.md b/docs/api/cn/http.md index 6d73ffc3d..db6443d1d 100644 --- a/docs/api/cn/http.md +++ b/docs/api/cn/http.md @@ -307,7 +307,7 @@ Neuron 将为 IIoT 平台提供一系列 API 服务,用于查询基本信息 **type** 必需 -**plugin** 可选 +**plugin** 可选,支持逗号分隔多个插件名,如 `plugin=Modbus TCP,Modbus RTU` **node** 可选 diff --git a/docs/api/english/http.md b/docs/api/english/http.md index 22233bc31..f3fe1190d 100644 --- a/docs/api/english/http.md +++ b/docs/api/english/http.md @@ -299,7 +299,7 @@ Neuron provide a series of API services for IIoT platform, to query the basic in **type** required -**plugin** optional +**plugin** optional, support multiple plugin names separated by comma, e.g. `plugin=Modbus TCP,Modbus RTU` **node** optional diff --git a/include/neuron/define.h b/include/neuron/define.h index 6859c92e1..59fc4fd7d 100644 --- a/include/neuron/define.h +++ b/include/neuron/define.h @@ -40,6 +40,7 @@ #define NEU_DEFAULT_GROUP_INTERVAL 100 #define NEU_NODE_NAME_LEN 128 #define NEU_PLUGIN_NAME_LEN 32 +#define NEU_PLUGIN_FILTER_MAX 16 #define NEU_NODE_TAGS_LEN 256 #define NEU_PLUGIN_LIBRARY_LEN 64 #define NEU_PLUGIN_DESCRIPTION_LEN 512 diff --git a/include/neuron/msg.h b/include/neuron/msg.h index 206fcd95c..b9bc4e4dd 100644 --- a/include/neuron/msg.h +++ b/include/neuron/msg.h @@ -556,7 +556,8 @@ typedef struct neu_req_del_node { typedef struct neu_req_get_node { neu_node_type_e type; - char plugin[NEU_PLUGIN_NAME_LEN]; + int n_plugin; + char plugin[NEU_PLUGIN_FILTER_MAX][NEU_PLUGIN_NAME_LEN]; char node[NEU_NODE_NAME_LEN]; struct { diff --git a/plugins/restful/adapter_handle.c b/plugins/restful/adapter_handle.c index c555aed48..7e7abde6a 100644 --- a/plugins/restful/adapter_handle.c +++ b/plugins/restful/adapter_handle.c @@ -203,10 +203,10 @@ void handle_del_adapter(nng_aio *aio) void handle_get_adapter(nng_aio *aio) { - int ret = 0; - char plugin_name[NEU_PLUGIN_NAME_LEN] = { 0 }; - char node_name[NEU_NODE_NAME_LEN] = { 0 }; - char group_name[NEU_GROUP_NAME_LEN] = { 0 }; + int ret = 0; + char plugin_name[NEU_PLUGIN_FILTER_MAX * NEU_PLUGIN_NAME_LEN] = { 0 }; + char node_name[NEU_NODE_NAME_LEN] = { 0 }; + char group_name[NEU_GROUP_NAME_LEN] = { 0 }; neu_plugin_t * plugin = neu_rest_get_plugin(); neu_node_type_e node_type = { 0 }; @@ -226,9 +226,40 @@ void handle_get_adapter(nng_aio *aio) return; } - if (neu_http_get_param_str(aio, "plugin", plugin_name, - sizeof(plugin_name)) > 0) { - strncpy(cmd.plugin, plugin_name, NEU_PLUGIN_NAME_LEN - 1); + ssize_t plugin_len = + neu_http_get_param_str(aio, "plugin", plugin_name, sizeof(plugin_name)); + if (plugin_len > 0) { + if (plugin_len >= (ssize_t) sizeof(plugin_name)) { + NEU_JSON_RESPONSE_ERROR(NEU_ERR_PARAM_IS_WRONG, { + neu_http_response(aio, error_code.error, result_error); + }) + return; + } + + char *save = NULL; + char *p = strtok_r(plugin_name, ",", &save); + while (p != NULL) { + while (' ' == *p || '\t' == *p) { + p++; + } + size_t len = strlen(p); + while (len > 0 && (' ' == p[len - 1] || '\t' == p[len - 1])) { + p[--len] = '\0'; + } + if (len == 0) { + p = strtok_r(NULL, ",", &save); + continue; + } + if (cmd.n_plugin >= NEU_PLUGIN_FILTER_MAX) { + NEU_JSON_RESPONSE_ERROR(NEU_ERR_PARAM_IS_WRONG, { + neu_http_response(aio, error_code.error, result_error); + }) + return; + } + strncpy(cmd.plugin[cmd.n_plugin], p, NEU_PLUGIN_NAME_LEN - 1); + cmd.n_plugin++; + p = strtok_r(NULL, ",", &save); + } } if (neu_http_get_param_str(aio, "node", node_name, sizeof(node_name)) > 0) { diff --git a/src/core/manager.c b/src/core/manager.c index 60d4ef33c..79af60916 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -495,8 +495,11 @@ static int manager_loop(enum neu_event_io_type type, int fd, void *usr_data) neu_req_del_plugin_t *cmd = (neu_req_del_plugin_t *) &header[1]; neu_resp_error_t e = { 0 }; + char plugins[1][NEU_PLUGIN_NAME_LEN] = { 0 }; + strncpy(plugins[0], cmd->plugin, NEU_PLUGIN_NAME_LEN - 1); + UT_array *nodes = neu_manager_get_nodes( - manager, NEU_NA_TYPE_DRIVER | NEU_NA_TYPE_APP, cmd->plugin, "", + manager, NEU_NA_TYPE_DRIVER | NEU_NA_TYPE_APP, plugins, 1, "", false, false, 0, false, 0, ""); if (nodes != NULL) { @@ -690,8 +693,11 @@ static int manager_loop(enum neu_event_io_type type, int fd, void *usr_data) break; } + char plugins[1][NEU_PLUGIN_NAME_LEN] = { 0 }; + strncpy(plugins[0], module_name, NEU_PLUGIN_NAME_LEN - 1); + UT_array *nodes = neu_manager_get_nodes( - manager, NEU_NA_TYPE_DRIVER | NEU_NA_TYPE_APP, module_name, "", + manager, NEU_NA_TYPE_DRIVER | NEU_NA_TYPE_APP, plugins, 1, "", false, false, 0, false, 0, ""); if (nodes != NULL) { @@ -957,9 +963,9 @@ static int manager_loop(enum neu_event_io_type type, int fd, void *usr_data) case NEU_REQ_GET_NODE: { neu_req_get_node_t *cmd = (neu_req_get_node_t *) &header[1]; UT_array * nodes = neu_manager_get_nodes( - manager, cmd->type, cmd->plugin, cmd->node, cmd->query.s_delay, - cmd->query.q_state, cmd->query.state, cmd->query.q_link, - cmd->query.link, cmd->query.q_group_name); + manager, cmd->type, cmd->plugin, cmd->n_plugin, cmd->node, + cmd->query.s_delay, cmd->query.q_state, cmd->query.state, + cmd->query.q_link, cmd->query.link, cmd->query.q_group_name); neu_resp_get_node_t resp = { .nodes = nodes }; header->type = NEU_RESP_GET_NODE; diff --git a/src/core/manager_internal.c b/src/core/manager_internal.c index babbd4132..217225a75 100644 --- a/src/core/manager_internal.c +++ b/src/core/manager_internal.c @@ -113,15 +113,15 @@ int neu_manager_del_node(neu_manager_t *manager, const char *node_name) neu_node_manager_del(manager->node_manager, node_name); return NEU_ERR_SUCCESS; } - UT_array *neu_manager_get_nodes(neu_manager_t *manager, int type, - const char *plugin, const char *node, + const char (*plugins)[NEU_PLUGIN_NAME_LEN], + int n_plugins, const char *node, bool sort_delay, bool q_state, int state, bool q_link, int link, const char *q_group_name) { - return neu_node_manager_filter(manager->node_manager, type, plugin, node, - sort_delay, q_state, state, q_link, link, - q_group_name); + return neu_node_manager_filter(manager->node_manager, type, plugins, + n_plugins, node, sort_delay, q_state, state, + q_link, link, q_group_name); } int neu_manager_update_node_name(neu_manager_t *manager, const char *node, diff --git a/src/core/manager_internal.h b/src/core/manager_internal.h index 4419ffd60..4d212aca5 100644 --- a/src/core/manager_internal.h +++ b/src/core/manager_internal.h @@ -59,7 +59,8 @@ int neu_manager_add_node(neu_manager_t *manager, const char *node_name, bool load); int neu_manager_del_node(neu_manager_t *manager, const char *node_name); UT_array *neu_manager_get_nodes(neu_manager_t *manager, int type, - const char *plugin, const char *node, + const char (*plugins)[NEU_PLUGIN_NAME_LEN], + int n_plugins, const char *node, bool sort_delay, bool q_state, int state, bool q_link, int link, const char *q_group_name); diff --git a/src/core/node_manager.c b/src/core/node_manager.c index f3fd20747..b6d5d1423 100644 --- a/src/core/node_manager.c +++ b/src/core/node_manager.c @@ -272,7 +272,8 @@ static bool find_tag_in_tags(const char *tags, const char *tag) } UT_array *neu_node_manager_filter(neu_node_manager_t *mgr, int type, - const char *plugin, const char *node, + const char (*plugins)[NEU_PLUGIN_NAME_LEN], + int n_plugins, const char *node, bool sort_delay, bool q_state, int state, bool q_link, int link, const char *q_group_name) @@ -287,9 +288,18 @@ UT_array *neu_node_manager_filter(neu_node_manager_t *mgr, int type, { if (!el->is_static && el->display) { if (el->adapter->module->type & type) { - if (strlen(plugin) > 0 && - strcmp(el->adapter->module->module_name, plugin) != 0) { - continue; + if (n_plugins > 0) { + bool matched = false; + for (int i = 0; i < n_plugins; i++) { + if (strcmp(el->adapter->module->module_name, + plugins[i]) == 0) { + matched = true; + break; + } + } + if (!matched) { + continue; + } } if (strlen(node) > 0) { char *name_find = strstr(el->adapter->name, node); diff --git a/src/core/node_manager.h b/src/core/node_manager.h index 7ac2a2854..4e6b2c1dd 100644 --- a/src/core/node_manager.h +++ b/src/core/node_manager.h @@ -52,7 +52,8 @@ uint16_t neu_node_manager_size(neu_node_manager_t *mgr); // neu_resp_node_info array UT_array *neu_node_manager_get(neu_node_manager_t *mgr, int type); UT_array *neu_node_manager_filter(neu_node_manager_t *mgr, int type, - const char *plugin, const char *node, + const char (*plugins)[NEU_PLUGIN_NAME_LEN], + int n_plugins, const char *node, bool sort_delay, bool q_state, int state, bool q_link, int link, const char *q_group_name); diff --git a/tests/ft/neuron/api.py b/tests/ft/neuron/api.py index 7af1d2037..fe6a2fbed 100644 --- a/tests/ft/neuron/api.py +++ b/tests/ft/neuron/api.py @@ -94,11 +94,13 @@ def del_node(node, jwt=config.default_jwt): return requests.delete(url=config.BASE_URL + '/api/v2/node', headers={"Authorization": jwt}, json={"name": node}) -def get_nodes(type, jwt=config.default_jwt, group=""): - if group == "": - return requests.get(url=config.BASE_URL + '/api/v2/node', headers={"Authorization": jwt}, params={"type": type}) - else: - return requests.get(url=config.BASE_URL + '/api/v2/node?group=' + group, headers={"Authorization": jwt}, params={"type": type}) +def get_nodes(type, jwt=config.default_jwt, group="", plugins=None): + params = {"type": type} + if plugins: + params["plugin"] = ",".join(plugins) + if group != "": + params["group"] = group + return requests.get(url=config.BASE_URL + '/api/v2/node', headers={"Authorization": jwt}, params=params) def get_nodes_by_tags(type, tags, jwt=config.default_jwt): diff --git a/tests/ft/node/test_node.py b/tests/ft/node/test_node.py index 84c3fea94..129411e2c 100644 --- a/tests/ft/node/test_node.py +++ b/tests/ft/node/test_node.py @@ -59,6 +59,33 @@ def test_get_app(self): assert 200 == response.status_code assert "mqtt" == response.json()['nodes'][0]["name"] + @description(given="a driver node", when="get driver node by single plugin", then="get success") + def test_get_driver_by_single_plugin(self): + response = api.get_nodes(type=NEU_NODE_DRIVER, plugins=[PLUGIN_MODBUS_TCP]) + assert 200 == response.status_code + assert 1 == len(response.json()['nodes']) + assert "modbus-tcp" == response.json()['nodes'][0]["name"] + + @description(given="driver and app nodes", when="get driver nodes by multiple plugins", then="get success") + def test_get_driver_by_multiple_plugins(self): + response = api.get_nodes(type=NEU_NODE_DRIVER, plugins=[PLUGIN_MODBUS_TCP, PLUGIN_MODBUS_RTU]) + assert 200 == response.status_code + assert 1 == len(response.json()['nodes']) + assert "modbus-tcp" == response.json()['nodes'][0]["name"] + + @description(given="driver node and non-existent plugin", when="get driver node by plugins with unmatched", then="get success") + def test_get_driver_by_plugin_with_unmatched(self): + response = api.get_nodes(type=NEU_NODE_DRIVER, plugins=[PLUGIN_MODBUS_TCP, "non-existent-plugin"]) + assert 200 == response.status_code + assert 1 == len(response.json()['nodes']) + assert "modbus-tcp" == response.json()['nodes'][0]["name"] + + @description(given="driver node", when="get driver node by plugins no match", then="get empty") + def test_get_driver_by_plugin_no_match(self): + response = api.get_nodes(type=NEU_NODE_DRIVER, plugins=[PLUGIN_MODBUS_RTU]) + assert 200 == response.status_code + assert [] == response.json()['nodes'] + @description(given="existent driver node", when="update the node name to empty string", then="update failed") def test_update_node_name_to_empty(self): response = api.update_node(node="modbus-tcp", new_name="")