diff --git a/examples/message-board/message-board.js b/examples/message-board/message-board.js index a2d5ce6..de057f0 100644 --- a/examples/message-board/message-board.js +++ b/examples/message-board/message-board.js @@ -60,6 +60,9 @@ const interatctiveInterface = async () => { case 'status': checkAgentStatus(); break; + case 'list': + getList(); + break; case 'create': contractId = await askForInput('Contract ID (default:uuidv4)', uuidv4()); image = await askForInput('Image: 1=ubuntu(default) | 2=nodejs', "1"); @@ -296,7 +299,7 @@ const sendToAgent = (msg, res = null) => { try { let output = execSync(`${cliPath} json -m '${msg}'`, { stdio: 'pipe' }); let message = Buffer.from(output).toString(); - message = JSON.parse(message.substring(0, message.length - 2)); // Skipping the \n from the result. + message = JSON.parse(message.substring(0, message.length - 1)); // Skipping the \n from the result. console.log('Received: ', message); res && res.status((message.content && typeof message.content == 'string' && message.content.endsWith("error")) ? 500 : 200).send(message); } @@ -306,6 +309,21 @@ const sendToAgent = (msg, res = null) => { } } +const getList = (res = null) => { + try { + let output = execSync(`${cliPath} list`, { stdio: 'pipe' }); + let message = Buffer.from(output).toString(); + if (!res) + console.log('Received: ', message); + else + res.status(200).send(message); + } + catch (e) { + console.error(`Message sending error. ${e}`); + res && res.status(500).send(`Message sending error. ${e}`); + } +} + const checkAgentStatus = (res = null) => { try { let output = execSync(`${cliPath} status`, { stdio: 'pipe' }); @@ -338,6 +356,9 @@ const restApi = async () => { app.post("/status", (req, res) => { checkAgentStatus(res); }); + app.post("/list", (req, res) => { + getList(res); + }); app.post("/create", (req, res) => { const msg = { id, diff --git a/sashi-cli/cli-manager.cpp b/sashi-cli/cli-manager.cpp index 7a1ad49..034b3c0 100644 --- a/sashi-cli/cli-manager.cpp +++ b/sashi-cli/cli-manager.cpp @@ -6,6 +6,7 @@ namespace cli constexpr const char *SOCKET_NAME = "sa.sock"; // Name of the sashimono socket. constexpr const char *DATA_DIR = "/etc/sashimono"; // Sashimono data directory. constexpr const int BUFFER_SIZE = 4096; // Max read buffer size. + constexpr const char *LIST_FORMATTER_STR = "%-38s%-27s%-10s%-10s%-10s%s\n"; cli_context ctx; @@ -132,6 +133,51 @@ namespace cli return res; } + /** + * Print the list of instances in a tabular manner. + * @return 0 on success, -1 on error. + */ + int list() + { + std::string message; + if (write_to_socket("{\"type\": \"list\"}") == -1 || read_from_socket(message) == -1) + return -1; + + try + { + jsoncons::json d = jsoncons::json::parse(message, jsoncons::strict_json_parsing()); + if (!d.contains("type") || + d["type"].as() != "list_res" || + !d.contains("content") || + !d["content"].is_array()) + { + std::cerr << "Invalid response. " << jsoncons::pretty_print(d) << std::endl; + return -1; + } + + printf(LIST_FORMATTER_STR, "Name", "User", "UserPort", "MeshPort", "Status", "Image"); + printf(LIST_FORMATTER_STR, "====", "====", "========", "========", "======", "====="); + + for (const auto &instance : d["content"].array_range()) + { + printf(LIST_FORMATTER_STR, + instance["name"].as().data(), + instance["user"].as().data(), + std::to_string(instance["user_port"].as()).c_str(), + std::to_string(instance["peer_port"].as()).c_str(), + instance["status"].as().data(), + instance["image"].as().data()); + } + } + catch (const std::exception &e) + { + std::cerr << "JSON message parsing failed. " << e.what() << std::endl; + return -1; + } + + return 0; + } + /** * Close the socket and deinitialize. */ diff --git a/sashi-cli/cli-manager.hpp b/sashi-cli/cli-manager.hpp index efb1a21..b21490d 100644 --- a/sashi-cli/cli-manager.hpp +++ b/sashi-cli/cli-manager.hpp @@ -20,6 +20,8 @@ namespace cli int read_from_socket(std::string &message); + int list(); + void deinit(); } diff --git a/sashi-cli/main.cpp b/sashi-cli/main.cpp index 89c87c9..92b97d5 100644 --- a/sashi-cli/main.cpp +++ b/sashi-cli/main.cpp @@ -62,8 +62,9 @@ int parse_cmd(int argc, char **argv) app.set_help_all_flag("--help-all", "Expand all help"); // Initialize subcommands. - CLI::App *status = app.add_subcommand("status", "Check socket accessibility"); - CLI::App *json = app.add_subcommand("json", "JSON payload - Example: sashi json -m '{\"type\":\"\", ...}'"); + CLI::App *status = app.add_subcommand("status", "Check socket accessibility."); + CLI::App *list = app.add_subcommand("list", "List all instances."); + CLI::App *json = app.add_subcommand("json", "JSON payload. Example: sashi json -m '{\"type\":\"\", ...}'"); // Initialize options. std::string json_message; @@ -87,6 +88,19 @@ int parse_cmd(int argc, char **argv) cli::deinit(); return 0; } + else if (list->parsed()) + { + if (cli::init(exec_dir) == -1) + return -1; + if (cli::list() == -1) + { + std::cerr << "Failed to list instances." << std::endl; + cli::deinit(); + return -1; + } + cli::deinit(); + return 0; + } else if (json->parsed() && !json_message.empty()) { std::string output; diff --git a/sashi-cli/pchheader.hpp b/sashi-cli/pchheader.hpp index eaddb87..c689948 100644 --- a/sashi-cli/pchheader.hpp +++ b/sashi-cli/pchheader.hpp @@ -11,5 +11,6 @@ #include #include #include +#include #endif \ No newline at end of file diff --git a/src/comm/comm_handler.cpp b/src/comm/comm_handler.cpp index 889b44f..2f3d9c3 100644 --- a/src/comm/comm_handler.cpp +++ b/src/comm/comm_handler.cpp @@ -2,12 +2,12 @@ #include "../util/util.hpp" #include "../conf.hpp" -#define __HANDLE_RESPONSE(type, content, ret) \ - { \ - std::string res; \ - msg_parser.build_response(res, type, content, type == msg::MSGTYPE_CREATE_RES && ret == 0); \ - send(res); \ - return ret; \ +#define __HANDLE_RESPONSE(type, content, ret) \ + { \ + std::string res; \ + msg_parser.build_response(res, type, content, (type == msg::MSGTYPE_CREATE_RES || type == msg::MSGTYPE_LIST_RES) && ret == 0); \ + send(res); \ + return ret; \ } namespace comm @@ -179,7 +179,15 @@ namespace comm // Clear the buffer after the message is parsed. read_buffer.clear(); - if (type == msg::MSGTYPE_CREATE) + if (type == msg::MSGTYPE_LIST) + { + std::vector instances; + hp::get_instance_list(instances); + std::string list_res; + msg_parser.build_list_response(list_res, instances); + __HANDLE_RESPONSE(msg::MSGTYPE_LIST_RES, list_res, 0); + } + else if (type == msg::MSGTYPE_CREATE) { msg::create_msg msg; if (msg_parser.extract_create_message(msg) == -1) @@ -253,7 +261,7 @@ namespace comm if (ctx.data_socket == -1) return -1; - const int ret = write(ctx.data_socket, message.data(), message.length() + 1); + const int ret = write(ctx.data_socket, message.data(), message.length()); // Close connection after sending the response to the client. disconnect(); return ret == -1 ? -1 : 0; diff --git a/src/hp_manager.cpp b/src/hp_manager.cpp index 1e706f3..d775263 100644 --- a/src/hp_manager.cpp +++ b/src/hp_manager.cpp @@ -21,8 +21,6 @@ namespace hp // Vector keeping vacant ports from destroyed instances. std::vector vacant_ports; - // This thread will monitor the status of the created instances. - std::thread hp_monitor_thread; bool is_shutting_down = false; conf::ugid contract_ugid; @@ -55,9 +53,6 @@ namespace hp // Populate the vacant ports vector with vacant ports of destroyed containers. sqlite::get_vacant_ports(db, vacant_ports); - // Monitor thread is temperory disabled until the implementation details are finalized. - // hp_monitor_thread = std::thread(hp_monitor_loop); - // Calculate the resources per instance. instance_resources.cpu_us = conf::cfg.system.max_cpu_us / conf::cfg.system.max_instance_count; instance_resources.mem_kbytes = conf::cfg.system.max_mem_kbytes / conf::cfg.system.max_instance_count; @@ -73,63 +68,11 @@ namespace hp void deinit() { is_shutting_down = true; - if (hp_monitor_thread.joinable()) - hp_monitor_thread.join(); if (db != NULL) sqlite::close_db(&db); } - /** - * Monitoring created container status. If any containers are crashed, then they are respawned. - * If the respawn fails, the current_status field is updated to 'exited' in the database. - */ - void hp_monitor_loop() - { - LOG_INFO << "HP instance monitor started."; - std::vector> running_instances; - - util::mask_signal(); - - int counter = 0; - - while (!is_shutting_down) - { - // Check containers every 1 minute. One minute sleep is not added because if we do so, app will wait until the full - // time until the app closes in a SIGINT. - if (counter == 0 || counter == 600) - { - sqlite::get_running_instance_user_and_name_list(db, running_instances); - for (const auto &[username, name] : running_instances) - { - std::string status; - const int res = check_instance_status(username, name, status); - if (res == 0 && status != CONTAINER_STATES[STATES::RUNNING]) - { - if (docker_start(username, name) == -1) - { - // We only change the current status variable from the monitor loop. - // We try to start this container in next iteration as well untill the desired state is achieved. - if (sqlite::update_current_status_in_container(db, name, CONTAINER_STATES[STATES::EXITED]) == 0) - LOG_INFO << "Re-spinning " + name + " failed. Current status updated to 'exited' in DB."; - } - else - { - // Make the current field NULL because the instance is healthy now. - if (sqlite::update_current_status_in_container(db, name, {}) == 0) - LOG_INFO << "Re-spinning " + name + " successful."; - } - } - } - counter = 0; - } - counter++; - util::sleep(100); - } - - LOG_INFO << "HP instance monitor stopped."; - } - /** * Create a new instance of hotpocket. A new contract is created with docker image. * @param info Structure holding the generated instance info. @@ -328,6 +271,7 @@ namespace hp info.container_name = container_name; info.contract_dir = contract_dir; + info.image_name = image_name; return 0; } @@ -943,4 +887,13 @@ namespace hp } } + /** + * Get the instance list except destroyed instances from the database. + * @param instances List of instances to be populated. + */ + void get_instance_list(std::vector &instances) + { + sqlite::get_instance_list(db, instances); + } + } // namespace hp diff --git a/src/hp_manager.hpp b/src/hp_manager.hpp index 62f498e..7abe6ec 100644 --- a/src/hp_manager.hpp +++ b/src/hp_manager.hpp @@ -43,6 +43,7 @@ namespace hp ports assigned_ports; std::string status; std::string username; + std::string image_name; }; struct resources @@ -56,8 +57,6 @@ namespace hp void deinit(); - void hp_monitor_loop(); - int create_new_instance(instance_info &info, std::string_view owner_pubkey, const std::string &contract_id, const std::string &image_key); int initiate_instance(std::string_view container_name, const msg::initiate_msg &config_msg); @@ -88,5 +87,8 @@ namespace hp int install_user(int &user_id, std::string &username, const size_t max_cpu_us, const size_t max_mem_kbytes, const size_t storage_kbytes, const std::string container_name); int uninstall_user(std::string_view username); + + void get_instance_list(std::vector &instances); + } // namespace hp #endif \ No newline at end of file diff --git a/src/msg/json/msg_json.cpp b/src/msg/json/msg_json.cpp index 0e8d3a0..42b557a 100644 --- a/src/msg/json/msg_json.cpp +++ b/src/msg/json/msg_json.cpp @@ -588,4 +588,59 @@ namespace msg::json msg += "\"}"; } + /** + * Constructs the response message for list message. + * @param msg Buffer to construct the generated json message string into. + * Message format: + * [ + * { + * "name": "", + * "user": "", + * "image": "", + * "status": "", + * "peer_port": "", + * "user_port": "" + * } + * ] + * @param instances Instance list. + * + */ + void build_list_response(std::string &msg, const std::vector &instances) + { + msg.reserve(1024); + msg += "["; + for (int i = 0; i < instances.size(); i++) + { + const hp::instance_info &instance = instances[i]; + msg += "{\""; + msg += "name"; + msg += SEP_COLON; + msg += instance.container_name; + msg += SEP_COMMA; + msg += "user"; + msg += SEP_COLON; + msg += instance.username; + msg += SEP_COMMA; + msg += "image"; + msg += SEP_COLON; + msg += instance.image_name; + msg += SEP_COMMA; + msg += "status"; + msg += SEP_COLON; + msg += instance.status; + msg += SEP_COMMA; + msg += "peer_port"; + msg += SEP_COLON_NOQUOTE; + msg += std::to_string(instance.assigned_ports.peer_port); + msg += SEP_COMMA_NOQUOTE; + msg += "user_port"; + msg += SEP_COLON_NOQUOTE; + msg += std::to_string(instance.assigned_ports.user_port); + msg += "}"; + if (i < instances.size() - 1) + msg += ","; + } + msg += "]"; + } + } // namespace msg::json \ No newline at end of file diff --git a/src/msg/json/msg_json.hpp b/src/msg/json/msg_json.hpp index 1927ac3..76e3b76 100644 --- a/src/msg/json/msg_json.hpp +++ b/src/msg/json/msg_json.hpp @@ -28,6 +28,8 @@ namespace msg::json void build_create_response(std::string &msg, const hp::instance_info &info); + void build_list_response(std::string &msg, const std::vector &instances); + } // namespace msg::json #endif \ No newline at end of file diff --git a/src/msg/msg_common.hpp b/src/msg/msg_common.hpp index 0136778..13471aa 100644 --- a/src/msg/msg_common.hpp +++ b/src/msg/msg_common.hpp @@ -178,6 +178,7 @@ namespace msg constexpr const char *MSGTYPE_DESTROY = "destroy"; constexpr const char *MSGTYPE_START = "start"; constexpr const char *MSGTYPE_STOP = "stop"; + constexpr const char *MSGTYPE_LIST = "list"; // Message res types constexpr const char *MSGTYPE_CREATE_RES = "create_res"; @@ -185,6 +186,7 @@ namespace msg constexpr const char *MSGTYPE_DESTROY_RES = "destroy_res"; constexpr const char *MSGTYPE_START_RES = "start_res"; constexpr const char *MSGTYPE_STOP_RES = "stop_res"; + constexpr const char *MSGTYPE_LIST_RES = "list_res"; } // namespace msg diff --git a/src/msg/msg_parser.cpp b/src/msg/msg_parser.cpp index 30309e4..f29c3cf 100644 --- a/src/msg/msg_parser.cpp +++ b/src/msg/msg_parser.cpp @@ -48,4 +48,9 @@ namespace msg json::build_create_response(msg, info); } + void msg_parser::build_list_response(std::string &msg, const std::vector &instances) const + { + json::build_list_response(msg, instances); + } + } // namespace msg \ No newline at end of file diff --git a/src/msg/msg_parser.hpp b/src/msg/msg_parser.hpp index e5e6ffc..f6dc0cb 100644 --- a/src/msg/msg_parser.hpp +++ b/src/msg/msg_parser.hpp @@ -21,6 +21,7 @@ namespace msg int extract_stop_message(stop_msg &msg) const; void build_response(std::string &msg, std::string_view response_type, std::string_view content, const bool json_content = false) const; void build_create_response(std::string &msg, const hp::instance_info &info) const; + void build_list_response(std::string &msg, const std::vector &instances) const; }; } // namespace msg diff --git a/src/sqlite.cpp b/src/sqlite.cpp index 96645fe..dfba8b5 100644 --- a/src/sqlite.cpp +++ b/src/sqlite.cpp @@ -22,8 +22,8 @@ namespace sqlite constexpr const char *INSERT_INTO_HP_INSTANCE = "INSERT INTO instances(" "owner_pubkey, time, username, status, name, ip," - "peer_port, user_port, pubkey, contract_id" - ") VALUES(?,?,?,?,?,?,?,?,?,?)"; + "peer_port, user_port, pubkey, contract_id, image_name" + ") VALUES(?,?,?,?,?,?,?,?,?,?,?)"; constexpr const char *GET_VACANT_PORTS_FROM_HP = "SELECT DISTINCT peer_port, user_port FROM " "instances WHERE status == ? AND user_port NOT IN" @@ -33,15 +33,13 @@ namespace sqlite constexpr const char *UPDATE_STATUS_IN_HP = "UPDATE instances SET status = ? WHERE name = ?"; - constexpr const char *UPDATE_CURRENT_STATUS_IN_HP = "UPDATE instances SET current_status = ? WHERE name = ?"; - constexpr const char *IS_CONTAINER_EXISTS = "SELECT username, status, peer_port, user_port FROM instances WHERE name = ?"; constexpr const char *GET_ALOCATED_INSTANCE_COUNT = "SELECT COUNT(name) FROM instances WHERE status != ?"; - + constexpr const char *GET_RUNNING_INSTANCE_NAMES = "SELECT name FROM instances WHERE status = ?"; - constexpr const char *GET_RUNNING_INSTANCE_USER_AND_NAME_LIST = "SELECT username,name FROM instances WHERE status = ?"; + constexpr const char *GET_INSTANCE_LIST = "SELECT name, username, user_port, peer_port, status, image_name FROM instances WHERE status != ?"; constexpr const char *IS_TABLE_EXISTS = "SELECT * FROM sqlite_master WHERE type='table' AND name = ?"; @@ -290,13 +288,13 @@ namespace sqlite table_column_info("time", COLUMN_DATA_TYPE::INT), table_column_info("username", COLUMN_DATA_TYPE::TEXT), table_column_info("status", COLUMN_DATA_TYPE::TEXT), - table_column_info("current_status", COLUMN_DATA_TYPE::TEXT), table_column_info("name", COLUMN_DATA_TYPE::TEXT, true), table_column_info("ip", COLUMN_DATA_TYPE::TEXT), table_column_info("peer_port", COLUMN_DATA_TYPE::INT), table_column_info("user_port", COLUMN_DATA_TYPE::INT), table_column_info("pubkey", COLUMN_DATA_TYPE::TEXT), - table_column_info("contract_id", COLUMN_DATA_TYPE::TEXT)}; + table_column_info("contract_id", COLUMN_DATA_TYPE::TEXT), + table_column_info("image_name", COLUMN_DATA_TYPE::TEXT)}; if (create_table(db, INSTANCE_TABLE, columns) == -1 || create_index(db, INSTANCE_TABLE, "name", true) == -1 || @@ -326,6 +324,7 @@ namespace sqlite sqlite3_bind_int64(stmt, 8, info.assigned_ports.user_port) == SQLITE_OK && sqlite3_bind_text(stmt, 9, info.pubkey.data(), info.pubkey.length(), SQLITE_STATIC) == SQLITE_OK && sqlite3_bind_text(stmt, 10, info.contract_id.data(), info.contract_id.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 11, info.image_name.data(), info.image_name.length(), SQLITE_STATIC) == SQLITE_OK && sqlite3_step(stmt) == SQLITE_DONE) { sqlite3_finalize(stmt); @@ -389,28 +388,6 @@ namespace sqlite return -1; } - /** - * Update the current status of the given container to the new value. - * @param db Database connection. - * @param container_name Name of the container whose status should be updated. - * @param current_status The new status of the container. - * @return 0 on success and -1 on error. - */ - int update_current_status_in_container(sqlite3 *db, std::string_view container_name, std::string_view current_status) - { - sqlite3_stmt *stmt; - if (sqlite3_prepare_v2(db, UPDATE_CURRENT_STATUS_IN_HP, -1, &stmt, 0) == SQLITE_OK && stmt != NULL && - sqlite3_bind_text(stmt, 1, current_status.data(), current_status.length(), SQLITE_STATIC) == SQLITE_OK && - sqlite3_bind_text(stmt, 2, container_name.data(), container_name.length(), SQLITE_STATIC) == SQLITE_OK && - sqlite3_step(stmt) == SQLITE_DONE) - { - sqlite3_finalize(stmt); - return 0; - } - LOG_ERROR << "Error updating container current status for " << container_name; - return -1; - } - /** * Get the max peer and user ports assigned for instances excluding destroyed instances. * @param db Database connection. @@ -493,25 +470,28 @@ namespace sqlite } /** - * Populate the given vector with user name and name of running hp instances. + * Populate the given vector with the instance list except destroyed instances. * @param db Database connection. - * @param running_instances Vector to hold user name and name of instances. + * @param running_instances Vector to hold instance details. */ - void get_running_instance_user_and_name_list(sqlite3 *db, std::vector> &running_instances) + void get_instance_list(sqlite3 *db, std::vector &instances) { - running_instances.clear(); - sqlite3_stmt *stmt; - std::string_view running_status(hp::CONTAINER_STATES[hp::STATES::RUNNING]); + std::string_view destroy_status(hp::CONTAINER_STATES[hp::STATES::DESTROYED]); - if (sqlite3_prepare_v2(db, GET_RUNNING_INSTANCE_USER_AND_NAME_LIST, -1, &stmt, 0) == SQLITE_OK && stmt != NULL && - sqlite3_bind_text(stmt, 1, running_status.data(), running_status.length(), SQLITE_STATIC) == SQLITE_OK) + if (sqlite3_prepare_v2(db, GET_INSTANCE_LIST, -1, &stmt, 0) == SQLITE_OK && stmt != NULL && + sqlite3_bind_text(stmt, 1, destroy_status.data(), destroy_status.length(), SQLITE_STATIC) == SQLITE_OK) { while (stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW) { - const std::string username(reinterpret_cast(sqlite3_column_text(stmt, 0))); - const std::string name(reinterpret_cast(sqlite3_column_text(stmt, 1))); - running_instances.push_back({username, name}); + hp::instance_info info; + info.container_name = reinterpret_cast(sqlite3_column_text(stmt, 0)); + info.username = reinterpret_cast(sqlite3_column_text(stmt, 1)); + info.assigned_ports.user_port = sqlite3_column_int64(stmt, 2); + info.assigned_ports.peer_port = sqlite3_column_int64(stmt, 3); + info.status = reinterpret_cast(sqlite3_column_text(stmt, 4)); + info.image_name = reinterpret_cast(sqlite3_column_text(stmt, 5)); + instances.push_back(info); } } diff --git a/src/sqlite.hpp b/src/sqlite.hpp index 83c31b5..a4b1262 100644 --- a/src/sqlite.hpp +++ b/src/sqlite.hpp @@ -69,15 +69,13 @@ namespace sqlite int update_status_in_container(sqlite3 *db, std::string_view container_name, std::string_view status); - int update_current_status_in_container(sqlite3 *db, std::string_view container_name, std::string_view status); - void get_max_ports(sqlite3 *db, hp::ports &max_ports); void get_vacant_ports(sqlite3 *db, std::vector &vacant_ports); void get_running_instance_names(sqlite3 *db, std::vector &running_instance_names); - void get_running_instance_user_and_name_list(sqlite3 *db, std::vector> &running_instances); + void get_instance_list(sqlite3 *db, std::vector &instances); int get_allocated_instance_count(sqlite3 *db); }