From fc8bb4bbee83461f595abece6fa078d7fb9c173d Mon Sep 17 00:00:00 2001 From: vsian Date: Thu, 10 Oct 2024 13:14:36 +0800 Subject: [PATCH] Http admin part2 (#2002) ### What problem does this PR solve? _HTTP APIs for admin mode: set node role, list node variables, list node configs, show node variable_ Issue link: #1937 ### Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Documentation Update --------- Signed-off-by: vsian --- docs/references/http_api_reference.mdx | 448 +++++++++++++++++++++++++ src/network/http_server.cpp | 352 ++++++++++++++----- 2 files changed, 711 insertions(+), 89 deletions(-) diff --git a/docs/references/http_api_reference.mdx b/docs/references/http_api_reference.mdx index 60fbc122ed..820d343407 100644 --- a/docs/references/http_api_reference.mdx +++ b/docs/references/http_api_reference.mdx @@ -2637,4 +2637,452 @@ A `500` HTTP status code indicates an error condition. The response includes a J +## Admin set node role + +**POST** `/admin/node/current` + +Set a node role. + +### Request + +- Method: POST +- URL: `/admin/node/current` +- Headers: + - `accept: application/json` + - `content-type: application/json` + +#### Request example + +```shell +curl --request POST \ + --url http://localhost:23822/admin/node/current \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + --data + '{ + "role" : "follower", + "name" : "following", + "address" : "0.0.0.0", + "port" : 23851 + }' +``` + +#### Request parameters + +- `role`: Required (*Body parameter*) + Indicating which role the user want the node to be, it must be set to `admin`, `standalone`, `leader` or `follower`. + - Parameter settings for setting a node to `admin` and `standalone` are both empty, no extra parameters are required. + - Parameter settings for setting a node to `leader`: + - "name" Required : Name of the node in the cluster. + - Parameter settings for setting a node to `follower` or `learner`: + - "name" Required : Name of the node in the cluster. + - "address" Required : Peer server address of the leader in the cluster. + - "port" Required : Peer server port of the leader in the cluster. + +### Response + + + + +The response includes a JSON object like the following: + +```shell +{ + "error_code": 0 +} +``` + +- `"error_code"`: `integer` + `0`: The operation succeeds. + + + + +A `500` HTTP status code indicates an error condition. The response includes a JSON object like the following: + +```shell +{ + "error_code":7020 + "error_message" : "Duplicate node: following" +} +``` + +- `"error_code"`: `integer` + A non-zero value indicates a specific error condition. +- `"error_message"`: `string` + When `error_code` is non-zero, `"error_message"` provides additional details about the error. + + + + +--- + +## Admin show node variables + +**GET** `/admin/variables` + +Show all node variables in admin mode. + +### Request + +- Method: GET +- URL: `/admin/variables` +- Headers: `accept: application/json` + +#### Request example + +```shell +curl --request GET \ + --url http://localhost:23821/admin/variables \ + --header 'accept: application/json' +``` + +#### Request parameters + +None. + +### Response + + + + +The response includes a JSON object like the following: + +```shell +{ + "error_code":0, + "role":"admin" +} +``` + +- `"error_code"`: `integer` + `0`: The operation succeeds. + + +--- + +## Admin show node configs + +**GET** `/admin/configs` + +Show all node configs in admin mode. + +### Request + +- Method: GET +- URL: `/admin/configs` +- Headers: `accept: application/json` + +#### Request example + +```shell +curl --request GET \ + --url http://localhost:23821/admin/configs \ + --header 'accept: application/json' +``` + +#### Request parameters + +None. + +### Response + +#### Status Code 200 + +The response includes a JSON object like the following: + +```shell +{ + "buffer_manager_size":"4294967296", + "cleanup_interval":"60", + "client_port":"23818", + "compact_interval":"120", + "connection_pool_size":"128", + "cpu_limit":"16", + "data_dir":"/var/infinity/leader/data", + "delta_checkpoint_interval":"60", + "delta_checkpoint_threshold":"67108864", + "error_code":0, + "full_checkpoint_interval":"86400", + "http_port":"23821", + "log_dir":"/var/infinity/leader/log", + "log_file_max_size":"10737418240", + "log_file_rotate_count":"10", + "log_filename":"infinity.log", + "log_level":"Debug", + "log_to_stdout":"true", + "mem_index_capacity":"1048576", + "optimize_interval":"10", + "peer_server_connection_pool_size":"64", + "postgres_port":"5433", + "record_running_query":"false", + "resource_dir":"/var/infinity/leader/resource", + "server_address":"0.0.0.0", + "temp_dir":"/var/infinity/leader/tmp", + "time_zone":"UTC-8", + "version":"0.4.0", + "wal_compact_threshold":"1073741824", + "wal_dir":"/var/infinity/leader/wal", + "wal_flush":"FlushAtOnce" +} +``` + + + +--- + +## Admin show node variable + +**GET** `/admin/variables/{variable_name}` + +Retrieves the value of a global variable in admin mode. + +### Request + +- Method: GET +- URL: `/admin/variables/{variable_name}` +- Headers: `accept: application/json` + +#### Request example + +```shell +curl --request GET \ + --url http://localhost:23820/admin/variables/{variable_name} \ + --header 'accept: application/json' +``` + +#### Request parameters + +- `variable_name`: (*Path parameter*) + The name of the variable. + +### Response + + + + +The response includes a JSON object like the following: + +```shell +{ + "error_code":0, + "server_role":"admin" +} +``` + +- `"error_code"`: `integer` + `0`: The operation succeeds. + + + + +The response includes a JSON object like the following: + +```shell +{ + "error_code" : 3027, + "error_message":"variable does not exist : role." +} +``` + +- `"error_code"`: `integer` + A non-zero value indicates a specific error condition. +- `"error_message"`: `string` + When `error_code` is non-zero, `"error_message"` provides additional details about the error. + + + + +--- + +## Admin show current node + +**GET** `/admin/node/current` + +Get information about the currently connected node. + +### Request + +- Method: GET +- URL: `/admin/node/{node_name}` +- Headers: `accept: application/json` + +#### Request example + +```shell +curl --request GET \ + --url http://localhost:23821/admin/node/learning \ + --header 'accept: application/json' +``` + +#### Request parameters + +None. + +### Response + + + +The response includes a JSON object like the following: + +```shell +{ + "error_code":0, + "node_role":"learner" +} +``` + +- `"error_code"`: `integer` + `0`: The operation succeeds. +- `"node_role": string` + The role of querying node. + + + + +--- + +## Admin show node + +**GET** `/admin/node/{node_name}` + +Get information about a node in the cluster. + +### Request + +- Method: GET +- URL: `/admin/node/{node_name}` +- Headers: `accept: application/json` + +#### Request example + +```shell +curl --request GET \ + --url http://localhost:23821/admin/node/learning \ + --header 'accept: application/json' +``` + +#### Request parameters + +None. + +### Response + + + +The response includes a JSON object like the following: + +```shell +{ + "error_code":0, + "node_role":"learner" +} +``` + +- `"error_code"`: `integer` + `0`: The operation succeeds. +- `"node_role": string` + The role of querying node. + + + + +A `500` HTTP status code indicates an error condition. The response includes a JSON object like the following: + +```shell +{ + "error_code":7019, + "error_msg":"Node doesn't exist: notlearning" +} +``` + +- `"error_code"`: `integer` + A non-zero value indicates a specific error condition. +- `"error_message"`: `string` + When `error_code` is non-zero, `"error_message"` provides additional details about the error. + + + + +--- + +## Admin list nodes + +**GET** `/admin/nodes` + +List all nodes in the cluster. + +### Request + +- Method: GET +- URL: `/admin/nodes` +- Headers: `accept: application/json` + +#### Request example + +```shell +curl --request GET \ + --url http://localhost:23822/admin/nodes \ + --header 'accept: application/json' +``` + +#### Request parameters + +None. + +### Response + + + + +The response includes a JSON object like the following: + +```shell +{ + "error_code":0, + "nodes":[ + ["following","follower"], + ["boss","leader"], + ["learning","learner"] + ] +} +``` + +- `"error_code"`: `integer` + `0`: The operation succeeds. +- `"nodes" : array` : + Each element is in `[nodename, noderole]` format. + + + + +--- + diff --git a/src/network/http_server.cpp b/src/network/http_server.cpp index 32d96f1b17..b325b4d13f 100644 --- a/src/network/http_server.cpp +++ b/src/network/http_server.cpp @@ -87,9 +87,9 @@ infinity::Status ParseColumnDefs(const nlohmann::json &fields, Vector column_type{nullptr}; SharedPtr type_info{nullptr}; - try{ + try { if (tokens[0] == "vector" || tokens[0] == "multivector" || tokens[0] == "tensor" || tokens[0] == "tensorarray") { - if(tokens.size() != 3) { + if (tokens.size() != 3) { return infinity::Status::ParserError("vector / multivector / tensor / tensorarray type syntax error"); } SizeT dimension = std::stoull(tokens[1]); @@ -125,7 +125,7 @@ infinity::Status ParseColumnDefs(const nlohmann::json &fields, Vector(logical_type_v, type_info); } else if (tokens[0] == "sparse") { - if(tokens.size() != 4) { + if (tokens.size() != 4) { return infinity::Status::ParserError("sparse type syntax error"); } SizeT dimension = std::stoull(tokens[1]); @@ -167,7 +167,7 @@ infinity::Status ParseColumnDefs(const nlohmann::json &fields, Vectortype()) { case LogicalType::kSparse: { default_expr = BuildConstantSparseExprFromJson(field_element["default"], - dynamic_cast(column_type->type_info().get())); + dynamic_cast(column_type->type_info().get())); break; } default: { @@ -461,7 +461,7 @@ class CreateTableHandler final : public HttpRequestHandler { } } - if(json_response["error_code"] == 0) { + if (json_response["error_code"] == 0) { auto result = infinity->CreateTable(database_name, table_name, column_definitions, table_constraint, options); if (result.IsOk()) { json_response["error_code"] = 0; @@ -632,10 +632,9 @@ class ExportTableHandler final : public HttpRequestHandler { export_options.copy_file_type_ = CopyFileType::kCSV; } else if (file_type_str == "jsonl") { export_options.copy_file_type_ = CopyFileType::kJSONL; - } else if (file_type_str == "fvecs"){ + } else if (file_type_str == "fvecs") { export_options.copy_file_type_ = CopyFileType::kFVECS; - } - else { + } else { json_response["error_code"] = ErrorCode::kNotSupported; json_response["error_message"] = fmt::format("Not supported file type {}", file_type_str); return ResponseFactory::createResponse(http_status, json_response.dump()); @@ -682,24 +681,24 @@ class ExportTableHandler final : public HttpRequestHandler { if (http_body_json.contains("columns")) { export_columns = new Vector(); - for(const auto& column: http_body_json["columns"]) { - if(column.is_string()) { + for (const auto &column : http_body_json["columns"]) { + if (column.is_string()) { String column_name = column; ToLower(column_name); - if(column_name == "_row_id") { - FunctionExpr* expr = new FunctionExpr(); + if (column_name == "_row_id") { + FunctionExpr *expr = new FunctionExpr(); expr->func_name_ = "row_id"; export_columns->emplace_back(expr); - } else if(column_name == "_create_timestamp") { - FunctionExpr* expr = new FunctionExpr(); + } else if (column_name == "_create_timestamp") { + FunctionExpr *expr = new FunctionExpr(); expr->func_name_ = "create_timestamp"; export_columns->emplace_back(expr); - } else if(column_name == "_delete_timestamp") { - FunctionExpr* expr = new FunctionExpr(); + } else if (column_name == "_delete_timestamp") { + FunctionExpr *expr = new FunctionExpr(); expr->func_name_ = "delete_timestamp"; export_columns->emplace_back(expr); } else { - ColumnExpr* expr = new ColumnExpr(); + ColumnExpr *expr = new ColumnExpr(); expr->names_.emplace_back(column_name); export_columns->emplace_back(expr); } @@ -978,7 +977,6 @@ class InsertHandler final : public HttpRequestHandler { } }); - for (SizeT idx = 0; idx < dimension; ++idx) { const auto &value_ref = value[idx]; const auto &value_type = value_ref.type(); @@ -1014,7 +1012,6 @@ class InsertHandler final : public HttpRequestHandler { } }); - for (SizeT idx = 0; idx < dimension; ++idx) { const auto &value_ref = value[idx]; const auto &value_type = value_ref.type(); @@ -1029,12 +1026,12 @@ class InsertHandler final : public HttpRequestHandler { values_row->emplace_back(const_expr); const_expr = nullptr; - } else if(first_elem_type == nlohmann::json::value_t::array) { - //std::cout<<"tensor"<long_array_.reserve(subdimension); - for(SizeT subidx = 0; subidx < subdimension; ++subidx) { + for (SizeT subidx = 0; subidx < subdimension; ++subidx) { const auto &value_ref = array[subidx]; const auto &value_type = value_ref.type(); switch (value_type) { @@ -1073,7 +1070,8 @@ class InsertHandler final : public HttpRequestHandler { } default: { json_response["error_code"] = ErrorCode::kInvalidEmbeddingDataType; - json_response["error_message"] = fmt::format("Tensor Embedding element type should be integer"); + json_response["error_message"] = + fmt::format("Tensor Embedding element type should be integer"); return ResponseFactory::createResponse(http_status, json_response.dump()); } } @@ -1083,7 +1081,7 @@ class InsertHandler final : public HttpRequestHandler { } values_row->emplace_back(const_expr); const_expr = nullptr; - } else if(first_elem_first_elem_type == nlohmann::json::value_t::number_float) { + } else if (first_elem_first_elem_type == nlohmann::json::value_t::number_float) { infinity::ConstantExpr *const_expr = new ConstantExpr(LiteralType::kSubArrayArray); DeferFn defer_free_tensor_array([&]() { if (const_expr != nullptr) { @@ -1108,7 +1106,7 @@ class InsertHandler final : public HttpRequestHandler { } }); const_expr_2->double_array_.reserve(subdimension); - for(SizeT subidx = 0; subidx < subdimension; ++subidx) { + for (SizeT subidx = 0; subidx < subdimension; ++subidx) { const auto &value_ref = array[subidx]; const auto &value_type = value_ref.type(); switch (value_type) { @@ -1128,12 +1126,12 @@ class InsertHandler final : public HttpRequestHandler { } values_row->emplace_back(const_expr); const_expr = nullptr; - } else if(first_elem_first_elem_type == nlohmann::json::value_t::array) { - //std::cout<<"tensorarray"<sub_array_array_.reserve(subdimension); - for(SizeT subidx = 0; subidx < subdimension; ++subidx) { + for (SizeT subidx = 0; subidx < subdimension; ++subidx) { const auto &subarray = array[subidx]; auto subsubdimension = subarray.size(); if (subsubdimension == 0) { @@ -1174,7 +1172,7 @@ class InsertHandler final : public HttpRequestHandler { } }); const_expr_3->long_array_.reserve(subsubdimension); - for(SizeT subsubidx = 0; subsubidx < subsubdimension; ++subsubidx) { + for (SizeT subsubidx = 0; subsubidx < subsubdimension; ++subsubidx) { const auto &value_ref = subarray[subsubidx]; const auto &value_type = value_ref.type(); switch (value_type) { @@ -1188,7 +1186,8 @@ class InsertHandler final : public HttpRequestHandler { } default: { json_response["error_code"] = ErrorCode::kInvalidEmbeddingDataType; - json_response["error_message"] = fmt::format("Tensor Embedding element type should be integer"); + json_response["error_message"] = + fmt::format("Tensor Embedding element type should be integer"); return ResponseFactory::createResponse(http_status, json_response.dump()); } } @@ -1201,7 +1200,7 @@ class InsertHandler final : public HttpRequestHandler { } values_row->emplace_back(const_expr); const_expr = nullptr; - } else if(first_elem_first_elem_first_elem_type == nlohmann::json::value_t::number_float) { + } else if (first_elem_first_elem_first_elem_type == nlohmann::json::value_t::number_float) { infinity::ConstantExpr *const_expr = new ConstantExpr(LiteralType::kSubArrayArray); DeferFn defer_free_tensor_array([&]() { if (const_expr != nullptr) { @@ -1226,7 +1225,7 @@ class InsertHandler final : public HttpRequestHandler { } }); const_expr_2->sub_array_array_.reserve(subdimension); - for(SizeT subidx = 0; subidx < subdimension; ++subidx) { + for (SizeT subidx = 0; subidx < subdimension; ++subidx) { const auto &subarray = array[subidx]; auto subsubdimension = subarray.size(); if (subsubdimension == 0) { @@ -1242,7 +1241,7 @@ class InsertHandler final : public HttpRequestHandler { } }); const_expr_3->double_array_.reserve(subsubdimension); - for(SizeT subsubidx = 0; subsubidx < subsubdimension; ++subsubidx) { + for (SizeT subsubidx = 0; subsubidx < subsubdimension; ++subsubidx) { const auto &value_ref = subarray[subsubidx]; const auto &value_type = value_ref.type(); switch (value_type) { @@ -1252,7 +1251,8 @@ class InsertHandler final : public HttpRequestHandler { } default: { json_response["error_code"] = ErrorCode::kInvalidEmbeddingDataType; - json_response["error_message"] = fmt::format("Tensor Embedding element type should be float"); + json_response["error_message"] = + fmt::format("Tensor Embedding element type should be float"); return ResponseFactory::createResponse(http_status, json_response.dump()); } } @@ -1277,7 +1277,8 @@ class InsertHandler final : public HttpRequestHandler { } } else { json_response["error_code"] = ErrorCode::kInvalidEmbeddingDataType; - json_response["error_message"] = fmt::format("Embedding element type can only be integer or float or tensor or tensor array"); + json_response["error_message"] = + fmt::format("Embedding element type can only be integer or float or tensor or tensor array"); return ResponseFactory::createResponse(http_status, json_response.dump()); } @@ -1508,12 +1509,12 @@ class InsertHandler final : public HttpRequestHandler { (*values_row)[column_id] = const_expr; const_expr = nullptr; - } else if(first_elem_type == nlohmann::json::value_t::array) { - //std::cout<<"tensor"<long_array_.reserve(subdimension); - for(SizeT subidx = 0; subidx < subdimension; ++subidx) { + for (SizeT subidx = 0; subidx < subdimension; ++subidx) { const auto &value_ref = array[subidx]; const auto &value_type = value_ref.type(); switch (value_type) { @@ -1552,7 +1553,8 @@ class InsertHandler final : public HttpRequestHandler { } default: { json_response["error_code"] = ErrorCode::kInvalidEmbeddingDataType; - json_response["error_message"] = fmt::format("Tensor Embedding element type should be integer"); + json_response["error_message"] = + fmt::format("Tensor Embedding element type should be integer"); return ResponseFactory::createResponse(http_status, json_response.dump()); } } @@ -1562,7 +1564,7 @@ class InsertHandler final : public HttpRequestHandler { } (*values_row)[column_id] = const_expr; const_expr = nullptr; - } else if(first_elem_first_elem_type == nlohmann::json::value_t::number_float) { + } else if (first_elem_first_elem_type == nlohmann::json::value_t::number_float) { infinity::ConstantExpr *const_expr = new ConstantExpr(LiteralType::kSubArrayArray); DeferFn defer_free_tensor_array([&]() { if (const_expr != nullptr) { @@ -1587,7 +1589,7 @@ class InsertHandler final : public HttpRequestHandler { } }); const_expr_2->double_array_.reserve(subdimension); - for(SizeT subidx = 0; subidx < subdimension; ++subidx) { + for (SizeT subidx = 0; subidx < subdimension; ++subidx) { const auto &value_ref = array[subidx]; const auto &value_type = value_ref.type(); switch (value_type) { @@ -1607,12 +1609,12 @@ class InsertHandler final : public HttpRequestHandler { } (*values_row)[column_id] = const_expr; const_expr = nullptr; - } else if(first_elem_first_elem_type == nlohmann::json::value_t::array) { - //std::cout<<"tensorarray"<sub_array_array_.reserve(subdimension); - for(SizeT subidx = 0; subidx < subdimension; ++subidx) { + for (SizeT subidx = 0; subidx < subdimension; ++subidx) { const auto &subarray = array[subidx]; auto subsubdimension = subarray.size(); if (subsubdimension == 0) { @@ -1653,7 +1655,7 @@ class InsertHandler final : public HttpRequestHandler { } }); const_expr_3->long_array_.reserve(subsubdimension); - for(SizeT subsubidx = 0; subsubidx < subsubdimension; ++subsubidx) { + for (SizeT subsubidx = 0; subsubidx < subsubdimension; ++subsubidx) { const auto &value_ref = subarray[subsubidx]; const auto &value_type = value_ref.type(); switch (value_type) { @@ -1667,7 +1669,8 @@ class InsertHandler final : public HttpRequestHandler { } default: { json_response["error_code"] = ErrorCode::kInvalidEmbeddingDataType; - json_response["error_message"] = fmt::format("Tensor Embedding element type should be integer"); + json_response["error_message"] = + fmt::format("Tensor Embedding element type should be integer"); return ResponseFactory::createResponse(http_status, json_response.dump()); } } @@ -1680,7 +1683,7 @@ class InsertHandler final : public HttpRequestHandler { } (*values_row)[column_id] = const_expr; const_expr = nullptr; - } else if(first_elem_first_elem_first_elem_type == nlohmann::json::value_t::number_float) { + } else if (first_elem_first_elem_first_elem_type == nlohmann::json::value_t::number_float) { infinity::ConstantExpr *const_expr = new ConstantExpr(LiteralType::kSubArrayArray); DeferFn defer_free_tensor_array([&]() { if (const_expr != nullptr) { @@ -1705,7 +1708,7 @@ class InsertHandler final : public HttpRequestHandler { } }); const_expr_2->sub_array_array_.reserve(subdimension); - for(SizeT subidx = 0; subidx < subdimension; ++subidx) { + for (SizeT subidx = 0; subidx < subdimension; ++subidx) { const auto &subarray = array[subidx]; auto subsubdimension = subarray.size(); if (subsubdimension == 0) { @@ -1721,7 +1724,7 @@ class InsertHandler final : public HttpRequestHandler { } }); const_expr_3->double_array_.reserve(subsubdimension); - for(SizeT subsubidx = 0; subsubidx < subsubdimension; ++subsubidx) { + for (SizeT subsubidx = 0; subsubidx < subsubdimension; ++subsubidx) { const auto &value_ref = subarray[subsubidx]; const auto &value_type = value_ref.type(); switch (value_type) { @@ -1731,7 +1734,8 @@ class InsertHandler final : public HttpRequestHandler { } default: { json_response["error_code"] = ErrorCode::kInvalidEmbeddingDataType; - json_response["error_message"] = fmt::format("Tensor Embedding element type should be float"); + json_response["error_message"] = + fmt::format("Tensor Embedding element type should be float"); return ResponseFactory::createResponse(http_status, json_response.dump()); } } @@ -1898,7 +1902,7 @@ class DeleteHandler final : public HttpRequestHandler { nlohmann::json http_body_json = nlohmann::json::parse(data_body); const String filter_string = http_body_json["filter"]; - if(filter_string != "") { + if (filter_string != "") { UniquePtr expr_parsed_result = MakeUnique(); ExprParser expr_parser; expr_parser.Parse(filter_string, expr_parsed_result.get()); @@ -1927,8 +1931,7 @@ class DeleteHandler final : public HttpRequestHandler { json_response["error_message"] = result.ErrorMsg(); http_status = HTTPStatus::CODE_500; } - } - else { + } else { auto database_name = request->getPathVariable("database_name"); auto table_name = request->getPathVariable("table_name"); const QueryResult result = infinity->Delete(database_name, table_name, nullptr); @@ -2059,7 +2062,7 @@ class UpdateHandler final : public HttpRequestHandler { for (SizeT idx = 0; idx < dimension; ++idx) { const auto &value_ref = value[idx]; const auto &value_type = value_ref.type(); - + switch (value_type) { case nlohmann::json::value_t::number_integer: { const_expr->long_array_.emplace_back(value_ref.template get()); @@ -2416,7 +2419,7 @@ class DropIndexHandler final : public HttpRequestHandler { } } - if(json_response["error_code"] == 0) { + if (json_response["error_code"] == 0) { auto result = infinity->DropIndex(database_name, table_name, index_name, options); if (result.IsOk()) { json_response["error_code"] = 0; @@ -2477,7 +2480,7 @@ class CreateIndexHandler final : public HttpRequestHandler { auto index_info = new IndexInfo(); DeferFn release_index_info([&]() { - if(index_info != nullptr) { + if (index_info != nullptr) { delete index_info; index_info = nullptr; } @@ -2487,7 +2490,7 @@ class CreateIndexHandler final : public HttpRequestHandler { ToLower(index_info->column_name_); auto index_param_list = new Vector(); DeferFn release_index_param_list([&]() { - if(index_param_list != nullptr) { + if (index_param_list != nullptr) { for (auto &index_param_ptr : *index_param_list) { delete index_param_ptr; } @@ -2523,7 +2526,7 @@ class CreateIndexHandler final : public HttpRequestHandler { index_param_list = nullptr; } - if(json_response["error_code"] == 0) { + if (json_response["error_code"] == 0) { auto result = infinity->CreateIndex(database_name, table_name, index_name, index_info, options); index_info = nullptr; if (result.IsOk()) { @@ -2560,13 +2563,13 @@ class OptimizeIndexHandler final : public HttpRequestHandler { OptimizeOptions optimize_options; optimize_options.index_name_ = index_name; if (body_info_json.contains("optimize_options")) { - if(body_info_json["optimize_options"].type() != nlohmann::json::value_t::object) { + if (body_info_json["optimize_options"].type() != nlohmann::json::value_t::object) { json_response["error_code"] = ErrorCode::kInvalidParameterValue; json_response["error_message"] = "Optimize options should be key value pairs!"; http_status = HTTPStatus::CODE_500; return ResponseFactory::createResponse(http_status, json_response.dump()); } - for(const auto &option : body_info_json["optimize_options"].items()) { + for (const auto &option : body_info_json["optimize_options"].items()) { const auto &key = option.key(); const auto &value = option.value(); auto *init_param = new InitParameter(); @@ -2893,7 +2896,6 @@ class ShowBlockColumnHandler final : public HttpRequestHandler { } }; - class ShowConfigsHandler final : public HttpRequestHandler { public: SharedPtr handle(const SharedPtr &request) final { @@ -2912,7 +2914,7 @@ class ShowConfigsHandler final : public HttpRequestHandler { for (int row = 0; row < row_count; ++row) { // config name Value name_value = data_block->GetValue(0, row); - const String& config_name = name_value.ToString(); + const String &config_name = name_value.ToString(); // config value Value value = data_block->GetValue(1, row); const String &config_value = value.ToString(); @@ -2969,12 +2971,13 @@ class ShowGlobalVariablesHandler final : public HttpRequestHandler { if (result.IsOk()) { json_response["error_code"] = 0; - DataBlock *data_block = result.result_table_->GetDataBlockById(0).get(); // Assume the variables output data only included in one data block + DataBlock *data_block = + result.result_table_->GetDataBlockById(0).get(); // Assume the variables output data only included in one data block auto row_count = data_block->row_count(); for (int row = 0; row < row_count; ++row) { // variable name Value name_value = data_block->GetValue(0, row); - const String& config_name = name_value.ToString(); + const String &config_name = name_value.ToString(); // variable value Value value = data_block->GetValue(1, row); const String &config_value = value.ToString(); @@ -3033,7 +3036,7 @@ class SetGlobalVariableHandler final : public HttpRequestHandler { try { nlohmann::json http_body_json = nlohmann::json::parse(data_body); - if(http_body_json.size() != 1) { + if (http_body_json.size() != 1) { json_response["error_code"] = 3076; json_response["error_message"] = "No variable will be set"; http_status = HTTPStatus::CODE_500; @@ -3109,12 +3112,13 @@ class ShowSessionVariablesHandler final : public HttpRequestHandler { if (result.IsOk()) { json_response["error_code"] = 0; - DataBlock *data_block = result.result_table_->GetDataBlockById(0).get(); // Assume the variables output data only included in one data block + DataBlock *data_block = + result.result_table_->GetDataBlockById(0).get(); // Assume the variables output data only included in one data block auto row_count = data_block->row_count(); for (int row = 0; row < row_count; ++row) { // variable name Value name_value = data_block->GetValue(0, row); - const String& config_name = name_value.ToString(); + const String &config_name = name_value.ToString(); // variable value Value value = data_block->GetValue(1, row); const String &config_value = value.ToString(); @@ -3173,7 +3177,7 @@ class SetSessionVariableHandler final : public HttpRequestHandler { try { nlohmann::json http_body_json = nlohmann::json::parse(data_body); - if(http_body_json.size() != 1) { + if (http_body_json.size() != 1) { json_response["error_code"] = 3076; json_response["error_message"] = "No variable will be set"; http_status = HTTPStatus::CODE_500; @@ -3250,7 +3254,7 @@ class SetConfigHandler final : public HttpRequestHandler { try { nlohmann::json http_body_json = nlohmann::json::parse(data_body); - if(http_body_json.size() != 1) { + if (http_body_json.size() != 1) { json_response["error_code"] = 3076; json_response["error_message"] = "No config will be set"; http_status = HTTPStatus::CODE_500; @@ -3511,15 +3515,15 @@ class ShowNodeByNameHandler final : public HttpRequestHandler { infinity::Status status; SharedPtr nodeinfo; std::tie(status, nodeinfo) = InfinityContext::instance().cluster_manager()->GetNodeInfoPtrByName(node_name); - if (status.ok()) { - http_status = HTTPStatus::CODE_200; + if (status.ok()) { + http_status = HTTPStatus::CODE_200; json_response["error_code"] = ErrorCode::kOk; json_response["node_role"] = ToString(nodeinfo->node_role_); } else { http_status = HTTPStatus::CODE_500; json_response["error_code"] = status.code(); json_response["error_msg"] = status.message(); - } + } return ResponseFactory::createResponse(http_status, json_response.dump()); } @@ -3536,7 +3540,7 @@ class ListAllNodesHandler final : public HttpRequestHandler { nlohmann::json nodes_json; Vector> nodes = InfinityContext::instance().cluster_manager()->ListNodes(); - for(const auto& ptr : nodes) { + for (const auto &ptr : nodes) { nodes_json.push_back({ptr->node_name_, ToString(ptr->node_role_)}); } @@ -3548,13 +3552,175 @@ class ListAllNodesHandler final : public HttpRequestHandler { } }; +class AdminSetNodeRoleHandler final : public HttpRequestHandler { +public: + SharedPtr handle(const SharedPtr &request) final { + auto infinity = Infinity::RemoteConnect(); + DeferFn defer_fn([&]() { infinity->RemoteDisconnect(); }); + + HTTPStatus http_status; + nlohmann::json json_response; + infinity::Status status; + + String data_body = request->readBodyToString(); + nlohmann::json http_body_json; + try { + http_body_json = nlohmann::json::parse(data_body); + } + catch (nlohmann::json::exception &e) { + http_status = HTTPStatus::CODE_500; + json_response["error_code"] = ErrorCode::kInvalidJsonFormat; + json_response["error_message"] = e.what(); + return ResponseFactory::createResponse(http_status, json_response.dump()); + } + + if (!http_body_json.contains("role") or !http_body_json["role"].is_string()) { + http_status = HTTPStatus::CODE_500; + json_response["error_code"] = ErrorCode::kInvalidCommand; + json_response["error_message"] = "field 'role' is required to be set to string!"; + return ResponseFactory::createResponse(http_status, json_response.dump()); + } + + String role = http_body_json["role"]; + ToLower(role); + if (role == "uninitialized") { + status = InfinityContext::instance().ChangeRole(NodeRole::kUnInitialized); + } else if (role == "admin") { + status = InfinityContext::instance().ChangeRole(NodeRole::kAdmin); + } else if (role == "standalone") { + status = InfinityContext::instance().ChangeRole(NodeRole::kStandalone); + } else if (role == "leader") { + status = InfinityContext::instance().ChangeRole(NodeRole::kLeader, http_body_json["name"]); + } else if (role == "follower") { + String node_name = http_body_json["name"]; + status = InfinityContext::instance().ChangeRole(NodeRole::kFollower, http_body_json["name"], http_body_json["address"], http_body_json["port"]); + } else if (role == "learner") { + String node_name = http_body_json["name"]; + status = InfinityContext::instance().ChangeRole(NodeRole::kLearner, http_body_json["name"], http_body_json["address"], http_body_json["port"]); + } else { + status = infinity::Status::InvalidNodeRole("invalid node role"); + } + + if (status.ok()) { + http_status = HTTPStatus::CODE_200; + json_response["error_code"] = ErrorCode::kOk; + } else { + http_status = HTTPStatus::CODE_500; + json_response["error_code"] = status.code(); + json_response["error_code"] = status.message(); + } + + return ResponseFactory::createResponse(http_status, json_response.dump()); + } +}; + +class AdminShowNodeVariablesHandler final : public HttpRequestHandler { +public: + SharedPtr handle(const SharedPtr &request) final { + auto infinity = Infinity::RemoteConnect(); + DeferFn defer_fn([&]() { infinity->RemoteDisconnect(); }); + + HTTPStatus http_status; + nlohmann::json json_response; + + auto result = infinity->AdminShowVariables(); + if (result.IsOk()) { + json_response["error_code"] = 0; + DataBlock *data_block = result.result_table_->GetDataBlockById(0).get(); // Assume the variables output data only included in one data block + auto row_count = data_block->row_count(); + for (int row = 0; row < row_count; ++row) { + // variable name + Value name_value = data_block->GetValue(0, row); + const String &config_name = name_value.ToString(); + // variable value + Value value = data_block->GetValue(1, row); + const String &config_value = value.ToString(); + json_response[config_name] = config_value; + } + http_status = HTTPStatus::CODE_200; + } else { + json_response["error_code"] = result.ErrorCode(); + json_response["error_message"] = result.ErrorMsg(); + http_status = HTTPStatus::CODE_500; + } + return ResponseFactory::createResponse(http_status, json_response.dump()); + } +}; + +class AdminShowNodeConfigsHandler final : public HttpRequestHandler { +public: + SharedPtr handle(const SharedPtr &request) final { + auto infinity = Infinity::RemoteConnect(); + DeferFn defer_fn([&]() { infinity->RemoteDisconnect(); }); + + HTTPStatus http_status; + nlohmann::json json_response; + + auto result = infinity->AdminShowConfigs(); + if (result.IsOk()) { + json_response["error_code"] = 0; + DataBlock *data_block = result.result_table_->GetDataBlockById(0).get(); // Assume the config output data only included in one data block + auto row_count = data_block->row_count(); + for (int row = 0; row < row_count; ++row) { + // config name + Value name_value = data_block->GetValue(0, row); + const String &config_name = name_value.ToString(); + // config value + Value value = data_block->GetValue(1, row); + const String &config_value = value.ToString(); + json_response[config_name] = config_value; + } + http_status = HTTPStatus::CODE_200; + } else { + json_response["error_code"] = result.ErrorCode(); + json_response["error_message"] = result.ErrorMsg(); + http_status = HTTPStatus::CODE_500; + } + return ResponseFactory::createResponse(http_status, json_response.dump()); + } +}; + +class AdminShowNodeVariableHandler final : public HttpRequestHandler { +public: + SharedPtr handle(const SharedPtr &request) final { + auto infinity = Infinity::RemoteConnect(); + DeferFn defer_fn([&]() { infinity->RemoteDisconnect(); }); + + String variable_name = request->getPathVariable("variable_name"); + auto result = infinity->AdminShowVariable(variable_name); + + HTTPStatus http_status; + nlohmann::json json_response; + + if (result.IsOk()) { + json_response["error_code"] = 0; + DataBlock *data_block = result.result_table_->GetDataBlockById(0).get(); + if (data_block->row_count() == 0) { + json_response["error_code"] = ErrorCode::kNoSuchSystemVar; + json_response["error_message"] = fmt::format("variable does not exist : {}.", variable_name); + http_status = HTTPStatus::CODE_500; + return ResponseFactory::createResponse(http_status, json_response.dump()); + } + Value value = data_block->GetValue(0, 0); + const String &variable_value = value.ToString(); + json_response[variable_name] = variable_value; + http_status = HTTPStatus::CODE_200; + } else { + json_response["error_code"] = result.ErrorCode(); + json_response["error_message"] = result.ErrorMsg(); + http_status = HTTPStatus::CODE_500; + } + return ResponseFactory::createResponse(http_status, json_response.dump()); + } +}; + } // namespace namespace infinity { -void HTTPServer::Start(const String& ip_address, u16 port) { - if(started_) { - return ; +void HTTPServer::Start(const String &ip_address, u16 port) { + if (started_) { + return; } WebEnvironment::init(); @@ -3588,8 +3754,12 @@ void HTTPServer::Start(const String& ip_address, u16 port) { // index router->route("GET", "/databases/{database_name}/tables/{table_name}/indexes", MakeShared()); router->route("GET", "/databases/{database_name}/tables/{table_name}/indexes/{index_name}", MakeShared()); - router->route("GET", "/databases/{database_name}/tables/{table_name}/indexes/{index_name}/segment/{segment_id}", MakeShared()); - router->route("GET", "/databases/{database_name}/tables/{table_name}/indexes/{index_name}/segment/{segment_id}/chunk/{chunk_id}", MakeShared()); + router->route("GET", + "/databases/{database_name}/tables/{table_name}/indexes/{index_name}/segment/{segment_id}", + MakeShared()); + router->route("GET", + "/databases/{database_name}/tables/{table_name}/indexes/{index_name}/segment/{segment_id}/chunk/{chunk_id}", + MakeShared()); router->route("DELETE", "/databases/{database_name}/tables/{table_name}/indexes/{index_name}", MakeShared()); router->route("POST", "/databases/{database_name}/tables/{table_name}/indexes/{index_name}", MakeShared()); router->route("PUT", "/databases/{database_name}/tables/{table_name}/indexes/{index_name}", MakeShared()); @@ -3631,7 +3801,11 @@ void HTTPServer::Start(const String& ip_address, u16 port) { router->route("GET", "/variables/session/{variable_name}", MakeShared()); router->route("SET", "/variables/session", MakeShared()); - //admin + // admin + router->route("POST", "/admin/node/current", MakeShared()); + router->route("GET", "/admin/variables", MakeShared()); + router->route("GET", "/admin/configs", MakeShared()); + router->route("GET", "/admin/variables/{variable_name}", MakeShared()); router->route("GET", "/admin/node/current", MakeShared()); router->route("GET", "/admin/node/{node_name}", MakeShared()); router->route("GET", "/admin/nodes", MakeShared()); @@ -3649,7 +3823,7 @@ void HTTPServer::Start(const String& ip_address, u16 port) { } void HTTPServer::Shutdown() { - if(started_) { + if (started_) { server_->stop(); WebEnvironment::destroy(); started_ = false;