diff --git a/include/neuron/msg.h b/include/neuron/msg.h index 9e677acea..6d68d9307 100644 --- a/include/neuron/msg.h +++ b/include/neuron/msg.h @@ -114,6 +114,8 @@ typedef enum neu_reqresp_type { NEU_RESP_UPDATE_TAG, NEU_REQ_GET_TAG, NEU_RESP_GET_TAG, + NEU_REQ_RENAME_TAG, + NEU_RESP_RENAME_TAG, NEU_REQ_ADD_PLUGIN, NEU_REQ_DEL_PLUGIN, @@ -169,6 +171,7 @@ typedef enum neu_reqresp_type { NEU_REQ_ADD_TAG_EVENT, NEU_REQ_DEL_TAG_EVENT, NEU_REQ_UPDATE_TAG_EVENT, + NEU_REQ_RENAME_TAG_EVENT, NEU_REQ_IMPORT_TAGS_EVENT, NEU_REQ_ADD_GTAG_EVENT, @@ -279,6 +282,8 @@ static const char *neu_reqresp_type_string_t[] = { [NEU_RESP_UPDATE_TAG] = "NEU_RESP_UPDATE_TAG", [NEU_REQ_GET_TAG] = "NEU_REQ_GET_TAG", [NEU_RESP_GET_TAG] = "NEU_RESP_GET_TAG", + [NEU_REQ_RENAME_TAG] = "NEU_REQ_RENAME_TAG", + [NEU_RESP_RENAME_TAG] = "NEU_RESP_RENAME_TAG", [NEU_REQ_ADD_PLUGIN] = "NEU_REQ_ADD_PLUGIN", [NEU_REQ_DEL_PLUGIN] = "NEU_REQ_DEL_PLUGIN", @@ -333,6 +338,7 @@ static const char *neu_reqresp_type_string_t[] = { [NEU_REQ_ADD_TAG_EVENT] = "NEU_REQ_ADD_TAG_EVENT", [NEU_REQ_DEL_TAG_EVENT] = "NEU_REQ_DEL_TAG_EVENT", [NEU_REQ_UPDATE_TAG_EVENT] = "NEU_REQ_UPDATE_TAG_EVENT", + [NEU_REQ_RENAME_TAG_EVENT] = "NEU_REQ_RENAME_TAG_EVENT", [NEU_REQ_IMPORT_TAGS_EVENT] = "NEU_REQ_IMPORT_TAGS_EVENT", [NEU_REQ_ADD_GTAG_EVENT] = "NEU_REQ_ADD_GTAG_EVENT", [NEU_REQ_ADD_PLUGIN_EVENT] = "NEU_REQ_ADD_PLUGIN_EVENT", @@ -730,6 +736,17 @@ typedef struct neu_resp_get_tag { UT_array *tags; // array neu_datatag_t } neu_resp_get_tag_t; +typedef struct { + char driver[NEU_NODE_NAME_LEN]; + char group[NEU_GROUP_NAME_LEN]; + char old_name[NEU_TAG_NAME_LEN]; + char new_name[NEU_TAG_NAME_LEN]; +} neu_req_rename_tag_t; + +typedef struct { + int error; +} neu_resp_rename_tag_t; + typedef struct { char app[NEU_NODE_NAME_LEN]; char driver[NEU_NODE_NAME_LEN]; diff --git a/include/neuron/persist/persist.h b/include/neuron/persist/persist.h index 6f0fd964a..c0211f895 100644 --- a/include/neuron/persist/persist.h +++ b/include/neuron/persist/persist.h @@ -333,6 +333,17 @@ int neu_persister_update_tag_value(const char * driver_name, int neu_persister_delete_tag(const char *driver_name, const char *group_name, const char *tag_name); +/** + * Rename node tag. + * @param driver_name name of the driver who owns the tags + * @param group_name name of the group + * @param old_name old name of the tag + * @param new_name new name of the tag + * @return 0 on success, non-zero otherwise + */ +int neu_persister_rename_tag(const char *driver_name, const char *group_name, + const char *old_name, const char *new_name); + /** * Persist subscriptions. * @param app_name name of the app node diff --git a/persistence/sqlite.db b/persistence/sqlite.db new file mode 100644 index 000000000..de6838f0a Binary files /dev/null and b/persistence/sqlite.db differ diff --git a/plugins/restful/datatag_handle.c b/plugins/restful/datatag_handle.c index 574ab283e..a9810a39d 100644 --- a/plugins/restful/datatag_handle.c +++ b/plugins/restful/datatag_handle.c @@ -398,6 +398,72 @@ void handle_update_tags_resp(nng_aio *aio, neu_resp_update_tag_t *resp) handle_add_tags_resp(aio, resp); } +void handle_rename_tag(nng_aio *aio) +{ + neu_plugin_t *plugin = neu_rest_get_plugin(); + + NEU_PROCESS_HTTP_REQUEST_VALIDATE_JWT( + aio, neu_json_rename_tag_req_t, neu_json_decode_rename_tag_req, { + int ret = 0; + neu_reqresp_head_t header = { 0 }; + neu_req_rename_tag_t cmd = { 0 }; + int err_type; + + header.ctx = aio; + header.type = NEU_REQ_RENAME_TAG; + header.otel_trace_type = NEU_OTEL_TRACE_TYPE_REST_COMM; + + if (strlen(req->node) >= NEU_NODE_NAME_LEN) { + err_type = NEU_ERR_NODE_NAME_TOO_LONG; + goto error; + } + + if (strlen(req->group) >= NEU_GROUP_NAME_LEN) { + err_type = NEU_ERR_GROUP_NAME_TOO_LONG; + goto error; + } + + if (strlen(req->old_name) == 0 || strlen(req->new_name) == 0) { + err_type = NEU_ERR_PARAM_IS_WRONG; + goto error; + } + + if (strlen(req->old_name) >= NEU_TAG_NAME_LEN) { + err_type = NEU_ERR_TAG_NAME_TOO_LONG; + goto error; + } + + if (strlen(req->new_name) >= NEU_TAG_NAME_LEN) { + err_type = NEU_ERR_TAG_NAME_TOO_LONG; + goto error; + } + + strcpy(cmd.driver, req->node); + strcpy(cmd.group, req->group); + strcpy(cmd.old_name, req->old_name); + strcpy(cmd.new_name, req->new_name); + + ret = neu_plugin_op(plugin, header, &cmd); + if (ret != 0) { + NEU_JSON_RESPONSE_ERROR(NEU_ERR_IS_BUSY, { + neu_http_response(aio, NEU_ERR_IS_BUSY, result_error); + }); + } + goto success; + + error: + NEU_JSON_RESPONSE_ERROR( + err_type, { neu_http_response(aio, err_type, result_error); }); + success:; + }) +} + +void handle_rename_tag_resp(nng_aio *aio, neu_resp_rename_tag_t *resp) +{ + NEU_JSON_RESPONSE_ERROR( + resp->error, { neu_http_response(aio, resp->error, result_error); }); +} + void handle_get_tags(nng_aio *aio) { neu_plugin_t * plugin = neu_rest_get_plugin(); diff --git a/plugins/restful/datatag_handle.h b/plugins/restful/datatag_handle.h index 4d754e8ed..3142221cd 100644 --- a/plugins/restful/datatag_handle.h +++ b/plugins/restful/datatag_handle.h @@ -33,6 +33,8 @@ void handle_import_tags_resp(nng_aio *aio, neu_resp_add_tag_t *resp); void handle_del_tags(nng_aio *aio); void handle_update_tags(nng_aio *aio); void handle_update_tags_resp(nng_aio *aio, neu_resp_update_tag_t *resp); +void handle_rename_tag(nng_aio *aio); +void handle_rename_tag_resp(nng_aio *aio, neu_resp_rename_tag_t *resp); void handle_get_tags(nng_aio *aio); void handle_get_tags_resp(nng_aio *aio, neu_resp_get_tag_t *tags); diff --git a/plugins/restful/handle.c b/plugins/restful/handle.c index cbb6ecb1a..d021db112 100644 --- a/plugins/restful/handle.c +++ b/plugins/restful/handle.c @@ -212,6 +212,9 @@ static struct neu_http_handler cors_handler[] = { { .url = "/api/v2/simulator/export", }, + { + .url = "/api/v2/tag/rename", + }, }; static struct neu_http_handler rest_handlers[] = { @@ -294,6 +297,12 @@ static struct neu_http_handler rest_handlers[] = { .url = "/api/v2/tags", .value.handler = handle_del_tags, }, + { + .method = NEU_HTTP_METHOD_PUT, + .type = NEU_HTTP_HANDLER_FUNCTION, + .url = "/api/v2/tag/rename", + .value.handler = handle_rename_tag, + }, { .method = NEU_HTTP_METHOD_POST, .type = NEU_HTTP_HANDLER_FUNCTION, diff --git a/plugins/restful/rest.c b/plugins/restful/rest.c index 645201456..0d6dc6331 100644 --- a/plugins/restful/rest.c +++ b/plugins/restful/rest.c @@ -280,6 +280,13 @@ static int dashb_plugin_request(neu_plugin_t * plugin, scope, "error", ((neu_resp_update_tag_t *) data)->error); } break; + case NEU_RESP_RENAME_TAG: + handle_rename_tag_resp(header->ctx, (neu_resp_rename_tag_t *) data); + if (neu_otel_control_is_started() && trace) { + neu_otel_scope_add_span_attr_int( + scope, "error", ((neu_resp_rename_tag_t *) data)->error); + } + break; case NEU_RESP_GET_TAG: handle_get_tags_resp(header->ctx, (neu_resp_get_tag_t *) data); if (neu_otel_control_is_started() && trace) { diff --git a/src/adapter/adapter.c b/src/adapter/adapter.c index 62c0136e5..b7ac6807a 100644 --- a/src/adapter/adapter.c +++ b/src/adapter/adapter.c @@ -548,6 +548,11 @@ static int adapter_command(neu_adapter_t *adapter, neu_reqresp_head_t header, strcpy(pheader->receiver, cmd->driver); break; } + case NEU_REQ_RENAME_TAG: { + neu_req_rename_tag_t *cmd = (neu_req_rename_tag_t *) data; + strcpy(pheader->receiver, cmd->driver); + break; + } case NEU_REQ_IMPORT_TAGS: { neu_req_import_tags_t *cmd = (neu_req_import_tags_t *) data; strcpy(pheader->receiver, cmd->node); @@ -887,6 +892,7 @@ static int adapter_loop(enum neu_event_io_type type, int fd, void *usr_data) case NEU_RESP_ADD_GTAG: case NEU_RESP_IMPORT_TAGS: case NEU_RESP_UPDATE_TAG: + case NEU_RESP_RENAME_TAG: case NEU_RESP_GET_TAG: case NEU_RESP_GET_NODE: case NEU_RESP_GET_PLUGIN: @@ -912,6 +918,7 @@ static int adapter_loop(enum neu_event_io_type type, int fd, void *usr_data) case NEU_REQ_ADD_TAG_EVENT: case NEU_REQ_DEL_TAG_EVENT: case NEU_REQ_UPDATE_TAG_EVENT: + case NEU_REQ_RENAME_TAG_EVENT: case NEU_REQ_ADD_GTAG_EVENT: case NEU_REQ_ADD_PLUGIN_EVENT: case NEU_REQ_DEL_PLUGIN_EVENT: @@ -1772,6 +1779,40 @@ static int adapter_loop(enum neu_event_io_type type, int fd, void *usr_data) reply(adapter, header, &resp); break; } + case NEU_REQ_RENAME_TAG: { + neu_req_rename_tag_t *cmd = (neu_req_rename_tag_t *) &header[1]; + neu_resp_rename_tag_t resp = { 0 }; + + if (adapter->module->type == NEU_NA_TYPE_DRIVER) { + resp.error = neu_adapter_driver_rename_tag( + (neu_adapter_driver_t *) adapter, cmd->group, cmd->old_name, + cmd->new_name); + if (resp.error == 0) { + rv = adapter_storage_rename_tag(cmd->driver, cmd->group, + cmd->old_name, cmd->new_name); + if (rv != 0) { + // rollback in-memory rename on persistence failure + nlog_error("persist rename failed, rolling back " + "tag:%s->%s node:%s grp:%s", + cmd->old_name, cmd->new_name, cmd->driver, + cmd->group); + neu_adapter_driver_rename_tag( + (neu_adapter_driver_t *) adapter, cmd->group, + cmd->new_name, cmd->old_name); + resp.error = NEU_ERR_EINTERNAL; + } else if (header->monitor) { + notify_monitor(adapter, NEU_REQ_RENAME_TAG_EVENT, cmd); + } + } + } else { + resp.error = NEU_ERR_GROUP_NOT_ALLOW; + } + + neu_msg_exchange(header); + header->type = NEU_RESP_RENAME_TAG; + reply(adapter, header, &resp); + break; + } case NEU_REQ_NODE_UNINIT: { neu_req_node_uninit_t *cmd = (neu_req_node_uninit_t *) &header[1]; char name[NEU_NODE_NAME_LEN] = { 0 }; diff --git a/src/adapter/driver/cache.c b/src/adapter/driver/cache.c index d76941bf0..d39a0c2f8 100644 --- a/src/adapter/driver/cache.c +++ b/src/adapter/driver/cache.c @@ -71,8 +71,8 @@ inline static tkey_t to_key(const char *group, const char *tag) { tkey_t key = { 0 }; - strcpy(key.group, group); - strcpy(key.tag, tag); + strncpy(key.group, group, sizeof(key.group) - 1); + strncpy(key.tag, tag, sizeof(key.tag) - 1); return key; } @@ -1266,3 +1266,21 @@ void neu_driver_cache_del(neu_driver_cache_t *cache, const char *group, pthread_mutex_unlock(&cache->mtx); } + +void neu_driver_cache_rename(neu_driver_cache_t *cache, const char *group, + const char *old_tag, const char *new_tag) +{ + struct elem *elem = NULL; + tkey_t key = to_key(group, old_tag); + + pthread_mutex_lock(&cache->mtx); + HASH_FIND(hh, cache->table, &key, sizeof(tkey_t), elem); + + if (elem != NULL) { + HASH_DEL(cache->table, elem); + elem->key = to_key(group, new_tag); + HASH_ADD(hh, cache->table, key, sizeof(tkey_t), elem); + } + + pthread_mutex_unlock(&cache->mtx); +} diff --git a/src/adapter/driver/cache.h b/src/adapter/driver/cache.h index 76c244429..dc7ff6147 100644 --- a/src/adapter/driver/cache.h +++ b/src/adapter/driver/cache.h @@ -43,6 +43,8 @@ bool neu_driver_cache_update_change(neu_driver_cache_t *cache, void neu_driver_cache_del(neu_driver_cache_t *cache, const char *group, const char *tag); +void neu_driver_cache_rename(neu_driver_cache_t *cache, const char *group, + const char *old_tag, const char *new_tag); void neu_driver_cache_update_trace(neu_driver_cache_t *cache, const char *group, void *trace_ctx); diff --git a/src/adapter/driver/driver.c b/src/adapter/driver/driver.c index e29ae785a..60a5e5eba 100644 --- a/src/adapter/driver/driver.c +++ b/src/adapter/driver/driver.c @@ -2320,6 +2320,30 @@ int neu_adapter_driver_update_tag(neu_adapter_driver_t *driver, return ret; } +int neu_adapter_driver_rename_tag(neu_adapter_driver_t *driver, + const char *group, const char *old_name, + const char *new_name) +{ + int ret = NEU_ERR_SUCCESS; + group_t *find = NULL; + + if (strlen(new_name) >= NEU_TAG_NAME_LEN) { + return NEU_ERR_TAG_NAME_TOO_LONG; + } + + HASH_FIND_STR(driver->groups, group, find); + if (find != NULL) { + ret = neu_group_rename_tag(find->group, old_name, new_name); + if (ret == NEU_ERR_SUCCESS) { + neu_driver_cache_rename(driver->cache, group, old_name, new_name); + } + } else { + ret = NEU_ERR_GROUP_NOT_EXIST; + } + + return ret; +} + int neu_adapter_driver_get_tag(neu_adapter_driver_t *driver, const char *group, UT_array **tags) { diff --git a/src/adapter/driver/driver_internal.h b/src/adapter/driver/driver_internal.h index 4a1c217e0..21cb1abdf 100644 --- a/src/adapter/driver/driver_internal.h +++ b/src/adapter/driver/driver_internal.h @@ -78,6 +78,9 @@ int neu_adapter_driver_del_tag(neu_adapter_driver_t *driver, const char *group, const char *tag); int neu_adapter_driver_update_tag(neu_adapter_driver_t *driver, const char *group, neu_datatag_t *tag); +int neu_adapter_driver_rename_tag(neu_adapter_driver_t *driver, + const char *group, const char *old_name, + const char *new_name); int neu_adapter_driver_get_tag(neu_adapter_driver_t *driver, const char *group, UT_array **tags); UT_array *neu_adapter_driver_get_ptag(neu_adapter_driver_t *driver, diff --git a/src/adapter/storage.c b/src/adapter/storage.c index f658a5586..aa364ed82 100644 --- a/src/adapter/storage.c +++ b/src/adapter/storage.c @@ -127,6 +127,17 @@ void adapter_storage_del_tag(const char *node, const char *group, } } +int adapter_storage_rename_tag(const char *node, const char *group, + const char *old_name, const char *new_name) +{ + int rv = neu_persister_rename_tag(node, group, old_name, new_name); + if (0 != rv) { + nlog_error("fail rename tag:%s->%s adapter:%s grp:%s", old_name, + new_name, node, group); + } + return rv; +} + int adapter_load_setting(const char *node, char **setting) { int rv = neu_persister_load_node_setting(node, (const char **) setting); diff --git a/src/adapter/storage.h b/src/adapter/storage.h index 03a3ad5ac..a933ef8a1 100644 --- a/src/adapter/storage.h +++ b/src/adapter/storage.h @@ -41,6 +41,8 @@ void adapter_storage_update_tag_value(const char *node, const char *group, const neu_datatag_t *tag); void adapter_storage_del_tag(const char *node, const char *group, const char *name); +int adapter_storage_rename_tag(const char *node, const char *group, + const char *old_name, const char *new_name); int adapter_load_setting(const char *node, char **setting); int adapter_load_group_and_tag(neu_adapter_driver_t *driver); diff --git a/src/base/group.c b/src/base/group.c index 3dbdb03ee..a7cf21546 100644 --- a/src/base/group.c +++ b/src/base/group.c @@ -157,6 +157,56 @@ int neu_group_update_tag(neu_group_t *group, const neu_datatag_t *tag) return ret; } +int neu_group_rename_tag(neu_group_t *group, const char *old_name, + const char *new_name) +{ + tag_elem_t *el = NULL; + tag_elem_t *conflict = NULL; + int ret = NEU_ERR_TAG_NOT_EXIST; + + pthread_mutex_lock(&group->mtx); + HASH_FIND_STR(group->tags, old_name, el); + if (el == NULL) { + pthread_mutex_unlock(&group->mtx); + return ret; + } + + // short-circuit: renaming to the same name is a no-op + if (strcmp(old_name, new_name) == 0) { + pthread_mutex_unlock(&group->mtx); + return NEU_ERR_SUCCESS; + } + + HASH_FIND_STR(group->tags, new_name, conflict); + if (conflict != NULL) { + pthread_mutex_unlock(&group->mtx); + return NEU_ERR_TAG_NAME_CONFLICT; + } + + // allocate new names before freeing old ones to ensure atomicity on OOM + char *new_el_name = strdup(new_name); + char *new_tag_name = strdup(new_name); + if (new_el_name == NULL || new_tag_name == NULL) { + free(new_el_name); + free(new_tag_name); + pthread_mutex_unlock(&group->mtx); + return NEU_ERR_EINTERNAL; + } + + HASH_DEL(group->tags, el); + free(el->name); + el->name = new_el_name; + free(el->tag->name); + el->tag->name = new_tag_name; + HASH_ADD_STR(group->tags, name, el); + + update_timestamp(group); + ret = NEU_ERR_SUCCESS; + pthread_mutex_unlock(&group->mtx); + + return ret; +} + int neu_group_del_tag(neu_group_t *group, const char *tag_name) { tag_elem_t *el = NULL; diff --git a/src/base/group.h b/src/base/group.h index 6b20772fd..787970d77 100644 --- a/src/base/group.h +++ b/src/base/group.h @@ -37,6 +37,8 @@ void neu_group_destroy(neu_group_t *group); int neu_group_update(neu_group_t *group, uint32_t interval); int neu_group_add_tag(neu_group_t *group, const neu_datatag_t *tag); int neu_group_update_tag(neu_group_t *group, const neu_datatag_t *tag); +int neu_group_rename_tag(neu_group_t *group, const char *old_name, + const char *new_name); int neu_group_del_tag(neu_group_t *group, const char *tag_name); UT_array * neu_group_get_tag(neu_group_t *group); UT_array * neu_group_query_tag(neu_group_t *group, const char *name); diff --git a/src/base/msg_internal.h b/src/base/msg_internal.h index 33fb59891..392d7fb1c 100644 --- a/src/base/msg_internal.h +++ b/src/base/msg_internal.h @@ -132,6 +132,7 @@ extern "C" { XX(NEU_REQ_ADD_TAG_EVENT, neu_req_add_tag_t) \ XX(NEU_REQ_DEL_TAG_EVENT, neu_req_del_tag_t) \ XX(NEU_REQ_UPDATE_TAG_EVENT, neu_req_update_tag_t) \ + XX(NEU_REQ_RENAME_TAG_EVENT, neu_req_rename_tag_t) \ XX(NEU_REQ_ADD_GTAG_EVENT, neu_req_add_gtag_t) \ XX(NEU_REQ_IMPORT_TAGS_EVENT, neu_req_import_tags_t) \ XX(NEU_REQ_ADD_PLUGIN_EVENT, neu_req_add_plugin_t) \ @@ -171,7 +172,9 @@ extern "C" { XX(NEU_REQ_SERVER_AUTH_USER_DELETE, neu_req_server_auth_user_del_t) \ XX(NEU_REQ_SERVER_AUTH_USER_UPDATE_PWD, neu_req_server_auth_user_update_t) \ XX(NEU_REQ_SERVER_AUTH_USER_INFO, neu_req_server_auth_users_info_t) \ - XX(NEU_RESP_SERVER_AUTH_USER_INFO, neu_resp_server_auth_users_info_t) + XX(NEU_RESP_SERVER_AUTH_USER_INFO, neu_resp_server_auth_users_info_t) \ + XX(NEU_REQ_RENAME_TAG, neu_req_rename_tag_t) \ + XX(NEU_RESP_RENAME_TAG, neu_resp_rename_tag_t) static inline size_t neu_reqresp_size(neu_reqresp_type_e t) { diff --git a/src/core/manager.c b/src/core/manager.c index 5a36f0fd7..4a974bd79 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -1515,6 +1515,19 @@ static int manager_loop(enum neu_event_io_type type, int fd, void *usr_data) break; } + case NEU_REQ_RENAME_TAG: { + if (neu_node_manager_find(manager->node_manager, header->receiver) == + NULL) { + neu_resp_error_t e = { .error = NEU_ERR_NODE_NOT_EXIST }; + header->type = NEU_RESP_ERROR; + neu_msg_exchange(header); + reply(manager, header, &e); + } else { + forward_msg(manager, header, header->receiver); + } + + break; + } case NEU_REQ_ADD_GTAG: { neu_req_add_gtag_t *cmd = (neu_req_add_gtag_t *) &header[1]; @@ -1593,6 +1606,7 @@ static int manager_loop(enum neu_event_io_type type, int fd, void *usr_data) case NEU_RESP_SERVER_SECURITY_POLICY_STATUS: case NEU_RESP_SERVER_AUTH_SWITCH_STATUS: case NEU_RESP_SERVER_AUTH_USER_INFO: + case NEU_RESP_RENAME_TAG: forward_msg(manager, header, header->receiver); break; @@ -1739,6 +1753,7 @@ static int manager_loop(enum neu_event_io_type type, int fd, void *usr_data) case NEU_REQ_ADD_TAG_EVENT: case NEU_REQ_DEL_TAG_EVENT: case NEU_REQ_UPDATE_TAG_EVENT: + case NEU_REQ_RENAME_TAG_EVENT: case NEU_REQ_ADD_GTAG_EVENT: case NEU_REQ_ADD_PLUGIN_EVENT: case NEU_REQ_DEL_PLUGIN_EVENT: diff --git a/src/parser/neu_json_tag.c b/src/parser/neu_json_tag.c index b1125f5e4..05280bbed 100644 --- a/src/parser/neu_json_tag.c +++ b/src/parser/neu_json_tag.c @@ -1018,3 +1018,79 @@ int neu_json_encode_import_tags_resp(void *json_object, void *param) return ret; } + +int neu_json_decode_rename_tag_req(char * buf, + neu_json_rename_tag_req_t **result) +{ + int ret = 0; + void * json_obj = NULL; + neu_json_rename_tag_req_t *req = + calloc(1, sizeof(neu_json_rename_tag_req_t)); + if (req == NULL) { + return -1; + } + + json_obj = neu_json_decode_new(buf); + if (NULL == json_obj) { + free(req); + return -1; + } + + neu_json_elem_t req_elems[] = { + { + .name = "node", + .t = NEU_JSON_STR, + }, + { + .name = "group", + .t = NEU_JSON_STR, + }, + { + .name = "old_name", + .t = NEU_JSON_STR, + }, + { + .name = "new_name", + .t = NEU_JSON_STR, + }, + }; + ret = neu_json_decode_by_json(json_obj, NEU_JSON_ELEM_SIZE(req_elems), + req_elems); + if (ret != 0) { + goto decode_fail; + } + + req->node = req_elems[0].v.val_str; + req->group = req_elems[1].v.val_str; + req->old_name = req_elems[2].v.val_str; + req->new_name = req_elems[3].v.val_str; + *result = req; + goto decode_exit; + +decode_fail: + free(req); + free(req_elems[0].v.val_str); + free(req_elems[1].v.val_str); + free(req_elems[2].v.val_str); + free(req_elems[3].v.val_str); + ret = -1; + +decode_exit: + if (json_obj != NULL) { + neu_json_decode_free(json_obj); + } + return ret; +} + +void neu_json_decode_rename_tag_req_free(neu_json_rename_tag_req_t *req) +{ + if (NULL == req) { + return; + } + + free(req->node); + free(req->group); + free(req->old_name); + free(req->new_name); + free(req); +} diff --git a/src/parser/neu_json_tag.h b/src/parser/neu_json_tag.h index bc98979e8..fd4762df0 100644 --- a/src/parser/neu_json_tag.h +++ b/src/parser/neu_json_tag.h @@ -159,6 +159,17 @@ typedef struct { int neu_json_encode_import_tags_resp(void *json_object, void *param); +typedef struct { + char *node; + char *group; + char *old_name; + char *new_name; +} neu_json_rename_tag_req_t; + +int neu_json_decode_rename_tag_req(char * buf, + neu_json_rename_tag_req_t **result); +void neu_json_decode_rename_tag_req_free(neu_json_rename_tag_req_t *req); + #ifdef __cplusplus } #endif diff --git a/src/persist/persist.c b/src/persist/persist.c index a24b730f1..385e77eab 100644 --- a/src/persist/persist.c +++ b/src/persist/persist.c @@ -393,6 +393,13 @@ int neu_persister_delete_tag(const char *driver_name, const char *group_name, return g_impl->vtbl->delete_tag(g_impl, driver_name, group_name, tag_name); } +int neu_persister_rename_tag(const char *driver_name, const char *group_name, + const char *old_name, const char *new_name) +{ + return g_impl->vtbl->rename_tag(g_impl, driver_name, group_name, old_name, + new_name); +} + int neu_persister_store_subscription(const char *app_name, const char *driver_name, const char *group_name, const char *params, diff --git a/src/persist/persist_impl.h b/src/persist/persist_impl.h index bf74f1b1b..63d6eb385 100644 --- a/src/persist/persist_impl.h +++ b/src/persist/persist_impl.h @@ -174,6 +174,18 @@ struct neu_persister_vtbl_s { int (*delete_tag)(neu_persister_t *self, const char *driver_name, const char *group_name, const char *tag_name); + /** + * Rename node tag. + * @param driver_name name of the driver who owns the tags + * @param group_name name of the group + * @param old_name old name of the tag + * @param new_name new name of the tag + * @return 0 on success, non-zero otherwise + */ + int (*rename_tag)(neu_persister_t *self, const char *driver_name, + const char *group_name, const char *old_name, + const char *new_name); + /** * Persist subscriptions. * @param app_name name of the app node diff --git a/src/persist/sqlite.c b/src/persist/sqlite.c index 20337e1ba..5c7202af3 100644 --- a/src/persist/sqlite.c +++ b/src/persist/sqlite.c @@ -422,6 +422,7 @@ static struct neu_persister_vtbl_s g_sqlite_persister_vtbl = { .update_tag = neu_sqlite_persister_update_tag, .update_tag_value = neu_sqlite_persister_update_tag_value, .delete_tag = neu_sqlite_persister_delete_tag, + .rename_tag = neu_sqlite_persister_rename_tag, .store_subscription = neu_sqlite_persister_store_subscription, .update_subscription = neu_sqlite_persister_update_subscription, .load_subscriptions = neu_sqlite_persister_load_subscriptions, @@ -890,6 +891,23 @@ int neu_sqlite_persister_delete_tag(neu_persister_t *self, return rv; } +int neu_sqlite_persister_rename_tag(neu_persister_t *self, + const char * driver_name, + const char * group_name, + const char *old_name, const char *new_name) +{ + sqlite3 *db = ((neu_sqlite_persister_t *) self)->db; + int rv = execute_sql(db, + "UPDATE tags SET name=%Q " + "WHERE driver_name=%Q AND group_name=%Q AND name=%Q", + new_name, driver_name, group_name, old_name); + if (0 == rv && 0 == sqlite3_changes(db)) { + nlog_warn("rename tag affected 0 rows: %s/%s %s->%s", driver_name, + group_name, old_name, new_name); + } + return rv; +} + int neu_sqlite_persister_store_subscription( neu_persister_t *self, const char *app_name, const char *driver_name, const char *group_name, const char *params, const char *static_tags) diff --git a/src/persist/sqlite.h b/src/persist/sqlite.h index aa4ecfca9..c9d8ba354 100644 --- a/src/persist/sqlite.h +++ b/src/persist/sqlite.h @@ -74,6 +74,10 @@ int neu_sqlite_persister_delete_tag(neu_persister_t *self, const char * driver_name, const char * group_name, const char * tag_name); +int neu_sqlite_persister_rename_tag(neu_persister_t *self, + const char * driver_name, + const char * group_name, + const char *old_name, const char *new_name); int neu_sqlite_persister_store_subscription( neu_persister_t *self, const char *app_name, const char *driver_name, const char *group_name, const char *params, const char *static_tags); diff --git a/tests/ft/neuron/api.py b/tests/ft/neuron/api.py index 4f17ade83..43ea79667 100644 --- a/tests/ft/neuron/api.py +++ b/tests/ft/neuron/api.py @@ -177,6 +177,10 @@ def get_tags(node, group): return requests.get(url=config.BASE_URL + "/api/v2/tags", headers={"Authorization": config.default_jwt}, params={"node": node, "group": group}) +def rename_tag(node, group, old_name, new_name): + return requests.put(url=config.BASE_URL + '/api/v2/tag/rename', headers={"Authorization": config.default_jwt}, json={"node": node, "group": group, "old_name": old_name, "new_name": new_name}) + + def read_tags(node, group, sync=False, query=None): body = {"node": node, "group": group, "sync": sync} if query: diff --git a/tests/ft/tag/test_tag.py b/tests/ft/tag/test_tag.py index 400362f52..32b0acce6 100644 --- a/tests/ft/tag/test_tag.py +++ b/tests/ft/tag/test_tag.py @@ -917,3 +917,99 @@ def test_add_string_tag_with_decimal(self): assert 400 == response.status_code assert NEU_ERR_TAG_DECIMAL_INVALID == response.json()['error'] assert 1 == response.json()['index'] + + @description(given="an existing tag", when="renaming to a new name", then="successfully renamed") + def test_rename_tag_success(self): + tag = [{"name": "rename_test_tag", "address": "1!400001", + "attribute": NEU_TAG_ATTRIBUTE_RW, "type": NEU_TYPE_INT16}] + api.add_tags(node="modbus-tcp", group="group1", tags=tag) + + response = api.rename_tag(node="modbus-tcp", group="group1", + old_name="rename_test_tag", new_name="renamed_tag") + assert 200 == response.status_code + assert NEU_ERR_SUCCESS == response.json()['error'] + + response = api.get_tags(node="modbus-tcp", group="group1") + assert 200 == response.status_code + tag_names = [t['name'] for t in response.json()['tags']] + assert "renamed_tag" in tag_names + assert "rename_test_tag" not in tag_names + + api.del_tags(node="modbus-tcp", group="group1", tags=["renamed_tag"]) + + @description(given="an existing tag", when="renaming to a name that already exists", then="fail with conflict error") + def test_rename_tag_conflict(self): + tag1 = [{"name": "rename_tag_a", "address": "1!400001", + "attribute": NEU_TAG_ATTRIBUTE_RW, "type": NEU_TYPE_INT16}] + tag2 = [{"name": "rename_tag_b", "address": "1!400002", + "attribute": NEU_TAG_ATTRIBUTE_RW, "type": NEU_TYPE_INT16}] + api.add_tags(node="modbus-tcp", group="group1", tags=tag1) + api.add_tags(node="modbus-tcp", group="group1", tags=tag2) + + response = api.rename_tag(node="modbus-tcp", group="group1", + old_name="rename_tag_a", new_name="rename_tag_b") + assert 409 == response.status_code + assert NEU_ERR_TAG_NAME_CONFLICT == response.json()['error'] + + api.del_tags(node="modbus-tcp", group="group1", tags=["rename_tag_a", "rename_tag_b"]) + + @description(given="a non-existing tag", when="renaming", then="fail with not exist error") + def test_rename_tag_not_exist(self): + response = api.rename_tag(node="modbus-tcp", group="group1", + old_name="non_exist_tag", new_name="new_name") + assert 404 == response.status_code + assert NEU_ERR_TAG_NOT_EXIST == response.json()['error'] + + @description(given="an existing tag", when="renaming to the same name", then="success as no-op") + def test_rename_tag_same_name(self): + tag = [{"name": "rename_same_tag", "address": "1!400001", + "attribute": NEU_TAG_ATTRIBUTE_RW, "type": NEU_TYPE_INT16}] + api.add_tags(node="modbus-tcp", group="group1", tags=tag) + + response = api.rename_tag(node="modbus-tcp", group="group1", + old_name="rename_same_tag", new_name="rename_same_tag") + assert 200 == response.status_code + assert NEU_ERR_SUCCESS == response.json()['error'] + + api.del_tags(node="modbus-tcp", group="group1", tags=["rename_same_tag"]) + + @description(given="an empty new_name", when="renaming", then="fail with name too long error") + def test_rename_tag_empty_new_name(self): + tag = [{"name": "rename_empty_tag", "address": "1!400001", + "attribute": NEU_TAG_ATTRIBUTE_RW, "type": NEU_TYPE_INT16}] + api.add_tags(node="modbus-tcp", group="group1", tags=tag) + + response = api.rename_tag(node="modbus-tcp", group="group1", + old_name="rename_empty_tag", new_name="") + assert 400 == response.status_code + assert NEU_ERR_PARAM_IS_WRONG == response.json()['error'] + + api.del_tags(node="modbus-tcp", group="group1", tags=["rename_empty_tag"]) + + @description(given="a new_name exceeding max length", when="renaming", then="fail with name too long error") + def test_rename_tag_name_too_long(self): + tag = [{"name": "rename_long_tag", "address": "1!400001", + "attribute": NEU_TAG_ATTRIBUTE_RW, "type": NEU_TYPE_INT16}] + api.add_tags(node="modbus-tcp", group="group1", tags=tag) + + long_name = "a" * 128 + response = api.rename_tag(node="modbus-tcp", group="group1", + old_name="rename_long_tag", new_name=long_name) + assert 400 == response.status_code + assert NEU_ERR_TAG_NAME_TOO_LONG == response.json()['error'] + + api.del_tags(node="modbus-tcp", group="group1", tags=["rename_long_tag"]) + + @description(given="a non-existing group", when="renaming tag", then="fail with group not exist") + def test_rename_tag_group_not_exist(self): + response = api.rename_tag(node="modbus-tcp", group="no_such_group", + old_name="some_tag", new_name="new_tag") + assert 404 == response.status_code + assert NEU_ERR_GROUP_NOT_EXIST == response.json()['error'] + + @description(given="a non-existing node", when="renaming tag", then="fail with node not exist") + def test_rename_tag_node_not_exist(self): + response = api.rename_tag(node="no_such_node", group="group1", + old_name="some_tag", new_name="new_tag") + assert 404 == response.status_code + assert NEU_ERR_NODE_NOT_EXIST == response.json()['error']