From 4625ce5cb70eae75511309cad7a49128aeefc516 Mon Sep 17 00:00:00 2001 From: Chalith Desaman Date: Thu, 13 Jan 2022 12:18:58 +0530 Subject: [PATCH] sashi attach command implementation (#90) --- sashi-cli/cli-manager.cpp | 108 ++++++++++++++++++++++++++++++++++++++ sashi-cli/cli-manager.hpp | 11 ++-- sashi-cli/main.cpp | 7 +++ src/comm/comm_handler.cpp | 36 +++++++++---- src/hp_manager.cpp | 20 +++++++ src/hp_manager.hpp | 2 + src/msg/json/msg_json.cpp | 76 +++++++++++++++++++++++++++ src/msg/json/msg_json.hpp | 4 ++ src/msg/msg_common.hpp | 9 ++++ src/msg/msg_parser.cpp | 10 ++++ src/msg/msg_parser.hpp | 2 + src/sqlite.cpp | 36 +++++++++++++ src/sqlite.hpp | 2 + 13 files changed, 309 insertions(+), 14 deletions(-) diff --git a/sashi-cli/cli-manager.cpp b/sashi-cli/cli-manager.cpp index 602ae31..027f302 100644 --- a/sashi-cli/cli-manager.cpp +++ b/sashi-cli/cli-manager.cpp @@ -4,13 +4,17 @@ namespace cli { constexpr const char *SOCKET_NAME = "sa.sock"; // Name of the sashimono socket. + constexpr const char *SAGENT_BIN_NAME = "sagent"; // Name of the sashimono agent bin. constexpr const char *DATA_DIR = "/etc/sashimono"; // Sashimono data directory. + constexpr const char *BIN_DIR = "/bin/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"; constexpr const char *MSG_LIST = "{\"type\": \"list\"}"; constexpr const char *MSG_BASIC = "{\"type\":\"%s\",\"container_name\":\"%s\"}"; constexpr const char *MSG_CREATE = "{\"type\":\"create\",\"owner_pubkey\":\"%s\",\"contract_id\":\"%s\",\"image\":\"%s\",\"config\":{}}"; + constexpr const char *DOCKER_ATTACH = "DOCKER_HOST=unix:///run/user/$(id -u %s)/docker.sock %s/dockerbin/docker attach %s"; + cli_context ctx; bool init_success = false; @@ -27,6 +31,10 @@ namespace cli if (get_socket_path(ctx.socket_path) == -1) return -1; + // Get the sashimono binary path from available location. + if (get_bin_path(ctx.sashimono_dir) == -1) + return -1; + // Create the seq paket socket. ctx.socket_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); if (ctx.socket_fd == -1) @@ -88,6 +96,38 @@ namespace cli return -1; } + /** + * Locate and return the sashimono agent binary path according predefined rules. + * If sagent found on the same path as the binary, use that. (to support dev testing) + * Else sagent found on /bin/sashimono, use that. + * Else show error. + * @param bin_path Binary path to be populated. + * @return 0 on success, -1 on error. + */ + int get_bin_path(std::string &bin_path) + { + // Check whether binary exists in exec path. + std::string path = ctx.sashi_dir + std::string("/") + SAGENT_BIN_NAME; + struct stat st; + if (stat(path.data(), &st) == 0 && S_ISREG(st.st_mode)) + { + bin_path = ctx.sashi_dir; + return 0; + } + + // Otherwise check in the bin dir. + path = BIN_DIR + std::string("/") + SAGENT_BIN_NAME; + memset(&st, 0, sizeof(struct stat)); + if (stat(path.data(), &st) == 0 && S_ISREG(st.st_mode)) + { + bin_path = BIN_DIR; + return 0; + } + + std::cerr << SAGENT_BIN_NAME << " is not found.\n"; + return -1; + } + /** * Write a given message into the sashimono socket. * @param message Message to be write. @@ -213,6 +253,74 @@ namespace cli return 0; } + /** + * Execute and docker command in a givent container. + * @param type Type of the command. + * @param container_name Name of the contract. + * @return 0 on success, -1 on error. + */ + int docker_exec(std::string_view type, std::string_view container_name) + { + std::string msg, output; + msg.resize(38 + container_name.size()); + sprintf(msg.data(), MSG_BASIC, "inspect", container_name.data()); + + const int ret = get_json_output(msg, output); + if (ret == -1) + { + std::cout << output << std::endl; + std::cerr << "Error inspecting the container." << std::endl; + return -1; + } + + std::string user; + try + { + jsoncons::json d = jsoncons::json::parse(output, jsoncons::strict_json_parsing()); + if (!d.contains("type") || + !d.contains("content") || + !((d["type"].as() == "inspect_res" && d["content"].is_object()) || (d["type"].as() == "inspect_error" && !d["content"].is_object()))) + { + std::cerr << "Invalid inspect response. " << jsoncons::pretty_print(d) << std::endl; + return -1; + } + + if (d["type"].as() == "inspect_error") + { + std::cerr << output << std::endl; + return -1; + } + + user = d["content"]["user"].as(); + } + catch (const std::exception &e) + { + std::cerr << "JSON message parsing failed. " << e.what() << std::endl; + return -1; + } + + if (user.empty()) + { + std::cerr << "Invalid user." << std::endl; + return -1; + } + + if (type == "attach") + { + const int len = 75 + user.length() + ctx.sashimono_dir.length() + container_name.length(); + char command[len]; + sprintf(command, DOCKER_ATTACH, user.data(), ctx.sashimono_dir.data(), container_name.data()); + return system(command) == 0 ? 0 : -1; + } + else + { + std::cerr << "Invalid docker command type." << 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 ba2e4c1..701328e 100644 --- a/sashi-cli/cli-manager.hpp +++ b/sashi-cli/cli-manager.hpp @@ -5,9 +5,10 @@ namespace cli { struct cli_context { - std::string sashi_dir; // Path of the Sashi CLI executable. - std::string socket_path; // Path of the sashimono socket. - int socket_fd = -1; // File descriptor of the socket. + std::string sashi_dir; // Path of the Sashi CLI executable. + std::string sashimono_dir; // Path of the Sashimono executable. + std::string socket_path; // Path of the sashimono socket. + int socket_fd = -1; // File descriptor of the socket. }; extern cli_context ctx; @@ -16,6 +17,8 @@ namespace cli int get_socket_path(std::string &socket_path); + int get_bin_path(std::string &bin_path); + int write_to_socket(std::string_view message); int read_from_socket(std::string &message); @@ -28,6 +31,8 @@ namespace cli int list(); + int docker_exec(std::string_view type, std::string_view container_name); + void deinit(); } diff --git a/sashi-cli/main.cpp b/sashi-cli/main.cpp index af590c5..e266901 100644 --- a/sashi-cli/main.cpp +++ b/sashi-cli/main.cpp @@ -83,6 +83,7 @@ int parse_cmd(int argc, char **argv) CLI::App *start = app.add_subcommand("start", "Starts an instance."); CLI::App *stop = app.add_subcommand("stop", "Stops an instance."); CLI::App *destroy = app.add_subcommand("destroy", "Destroys an instance."); + CLI::App *attach = app.add_subcommand("attach", "Attachs to the bash of a instance."); // Initialize options. std::string json_message; @@ -97,6 +98,7 @@ int parse_cmd(int argc, char **argv) start->add_option("-n,--name", container_name, "Instance name"); stop->add_option("-n,--name", container_name, "Instance name"); destroy->add_option("-n,--name", container_name, "Instance name"); + attach->add_option("-n,--name", container_name, "Instance name"); CLI11_PARSE(app, argc, argv); @@ -164,6 +166,11 @@ int parse_cmd(int argc, char **argv) return execute_cli([&]() { return cli::execute_basic("destroy", container_name); }); } + else if (attach->parsed() && !container_name.empty()) + { + return execute_cli([&]() + { return cli::docker_exec("attach", container_name); }); + } std::cout << app.help(); return -1; diff --git a/src/comm/comm_handler.cpp b/src/comm/comm_handler.cpp index 4a10197..5ae83b8 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 || type == msg::MSGTYPE_LIST_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 || type == msg::MSGTYPE_INSPECT_RES) && ret == 0); \ + send(res); \ + return ret; \ } namespace comm @@ -28,11 +28,10 @@ namespace comm constexpr const char *DESTROY_ERROR = "destroy_error"; struct Callback -{ - double execTime; - void (*func)(); -}; - + { + double execTime; + void (*func)(); + }; comm_ctx ctx; @@ -265,6 +264,21 @@ namespace comm __HANDLE_RESPONSE(msg::MSGTYPE_STOP_RES, "stopped", 0); } + else if (type == msg::MSGTYPE_INSPECT) + { + msg::inspect_msg msg; + if (msg_parser.extract_inspect_message(msg)) + __HANDLE_RESPONSE(msg::MSGTYPE_INSPECT_ERROR, FORMAT_ERROR, -1); + + hp::instance_info instance; + std::string error_msg; + if (hp::get_instance(error_msg, msg.container_name, instance) == -1) + __HANDLE_RESPONSE(msg::MSGTYPE_INSPECT_ERROR, error_msg, -1); + + std::string inspect_res; + msg_parser.build_inspect_response(inspect_res, instance); + __HANDLE_RESPONSE(msg::MSGTYPE_INSPECT_RES, inspect_res, 0); + } else __HANDLE_RESPONSE("error", TYPE_ERROR, -1); diff --git a/src/hp_manager.cpp b/src/hp_manager.cpp index b0fac08..ffb8828 100644 --- a/src/hp_manager.cpp +++ b/src/hp_manager.cpp @@ -45,6 +45,7 @@ namespace hp constexpr const char *MAX_ALLOCATION_REACHED = "max_alloc_reached"; constexpr const char *CONTRACT_ID_INVALID = "contractid_bad_format"; constexpr const char *DOCKER_IMAGE_INVALID = "docker_image_invalid"; + constexpr const char *DOCKER_CONTAINER_NOT_FOUND = "container_not_found"; // Cgrules check related constants. constexpr const char *CGRULE_ACTIVE = "service=$(grep \"ExecStart.*=.*/cgrulesengd$\" /etc/systemd/system/*.service | head -1 | awk -F : ' { print $1 } ') && [ ! -z $service ] && systemctl is-active $(basename $service)"; @@ -927,6 +928,25 @@ namespace hp sqlite::get_instance_list(db, instances); } + /** + * Get the instance with given name from the database, skip if destroyed. + * @param error_msg Error message if any. + * @param container_name Name of the instance + * @param instance Instance info ref to be populated. + * @return 0 on success and -1 on error. + */ + int get_instance(std::string &error_msg, std::string_view container_name, hp::instance_info &instance) + { + if (sqlite::get_instance(db, container_name, instance) == -1) + { + error_msg = DOCKER_CONTAINER_NOT_FOUND; + LOG_ERROR << "No instace with name: " << container_name << "."; + return -1; + } + + return 0; + } + /** * Check whether there's a pending reboot and cgrules service is running and configured. * @return true if active and configured otherwise false. diff --git a/src/hp_manager.hpp b/src/hp_manager.hpp index 605b396..8c0c1ce 100644 --- a/src/hp_manager.hpp +++ b/src/hp_manager.hpp @@ -91,6 +91,8 @@ namespace hp void get_instance_list(std::vector &instances); + int get_instance(std::string &error_msg, std::string_view container_name, hp::instance_info &instance); + bool system_ready(); } // namespace hp diff --git a/src/msg/json/msg_json.cpp b/src/msg/json/msg_json.cpp index d231229..335b8d6 100644 --- a/src/msg/json/msg_json.cpp +++ b/src/msg/json/msg_json.cpp @@ -516,6 +516,38 @@ namespace msg::json return 0; } + /** + * Extracts inspect message from msg. + * @param msg Populated msg object. + * @param d The json document holding the message. + * Accepted signed input container format: + * { + * "type": "inspect", + * "container_name": "", + * } + * @return 0 on successful extraction. -1 for failure. + */ + int extract_inspect_message(inspect_msg &msg, const jsoncons::json &d) + { + if (extract_type(msg.type, d) == -1) + return -1; + + if (!d.contains(msg::FLD_CONTAINER_NAME)) + { + LOG_ERROR << "Field container_name is missing."; + return -1; + } + + if (!d[msg::FLD_CONTAINER_NAME].is()) + { + LOG_ERROR << "Invalid container_name value."; + return -1; + } + + msg.container_name = d[msg::FLD_CONTAINER_NAME].as(); + return 0; + } + /** * Constructs a generic json response. * @param msg Buffer to construct the generated json message string into. @@ -647,4 +679,48 @@ namespace msg::json msg += "]"; } + /** + * Constructs the response message for inspect message. + * @param msg Buffer to construct the generated json message string into. + * Message format: + * { + * "name": "", + * "user": "", + * "image": "", + * "status": "", + * "peer_port": "", + * "user_port": "" + * } + * @param instance Instance info. + * + */ + void build_inspect_response(std::string &msg, const hp::instance_info &instance) + { + msg.reserve(1024); + 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 += "}"; + } } // 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 76e3b76..e5b836c 100644 --- a/src/msg/json/msg_json.hpp +++ b/src/msg/json/msg_json.hpp @@ -24,12 +24,16 @@ namespace msg::json int extract_stop_message(stop_msg &msg, const jsoncons::json &d); + int extract_inspect_message(inspect_msg &msg, const jsoncons::json &d); + void build_response(std::string &msg, std::string_view response_type, std::string_view content, const bool json_content = false); void build_create_response(std::string &msg, const hp::instance_info &info); void build_list_response(std::string &msg, const std::vector &instances); + void build_inspect_response(std::string &msg, const hp::instance_info &instance); + } // 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 35f0539..695fb65 100644 --- a/src/msg/msg_common.hpp +++ b/src/msg/msg_common.hpp @@ -129,6 +129,12 @@ namespace msg std::string container_name; }; + struct inspect_msg + { + std::string type; + std::string container_name; + }; + // Message field names constexpr const char *FLD_TYPE = "type"; constexpr const char *FLD_CONTENT = "content"; @@ -181,6 +187,7 @@ namespace msg constexpr const char *MSGTYPE_START = "start"; constexpr const char *MSGTYPE_STOP = "stop"; constexpr const char *MSGTYPE_LIST = "list"; + constexpr const char *MSGTYPE_INSPECT = "inspect"; // Message res types constexpr const char *MSGTYPE_CREATE_RES = "create_res"; @@ -190,6 +197,8 @@ namespace msg constexpr const char *MSGTYPE_START_RES = "start_res"; constexpr const char *MSGTYPE_STOP_RES = "stop_res"; constexpr const char *MSGTYPE_LIST_RES = "list_res"; + constexpr const char *MSGTYPE_INSPECT_RES = "inspect_res"; + constexpr const char *MSGTYPE_INSPECT_ERROR = "inspect_error"; } // namespace msg diff --git a/src/msg/msg_parser.cpp b/src/msg/msg_parser.cpp index f29c3cf..65760c6 100644 --- a/src/msg/msg_parser.cpp +++ b/src/msg/msg_parser.cpp @@ -38,6 +38,11 @@ namespace msg return json::extract_stop_message(msg, jdoc); } + int msg_parser::extract_inspect_message(inspect_msg &msg) const + { + return json::extract_inspect_message(msg, jdoc); + } + void msg_parser::build_response(std::string &msg, std::string_view response_type, std::string_view content, const bool json_content) const { json::build_response(msg, response_type, content, json_content); @@ -53,4 +58,9 @@ namespace msg json::build_list_response(msg, instances); } + void msg_parser::build_inspect_response(std::string &msg, const hp::instance_info &instance) const + { + json::build_inspect_response(msg, instance); + } + } // namespace msg \ No newline at end of file diff --git a/src/msg/msg_parser.hpp b/src/msg/msg_parser.hpp index f6dc0cb..f14d5cc 100644 --- a/src/msg/msg_parser.hpp +++ b/src/msg/msg_parser.hpp @@ -19,9 +19,11 @@ namespace msg int extract_destroy_message(destroy_msg &msg) const; int extract_start_message(start_msg &msg) const; int extract_stop_message(stop_msg &msg) const; + int extract_inspect_message(inspect_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; + void build_inspect_response(std::string &msg, const hp::instance_info &instance) const; }; } // namespace msg diff --git a/src/sqlite.cpp b/src/sqlite.cpp index dfba8b5..5491ff1 100644 --- a/src/sqlite.cpp +++ b/src/sqlite.cpp @@ -41,6 +41,8 @@ namespace sqlite constexpr const char *GET_INSTANCE_LIST = "SELECT name, username, user_port, peer_port, status, image_name FROM instances WHERE status != ?"; + constexpr const char *GET_INSTANCE = "SELECT name, username, user_port, peer_port, status, image_name FROM instances WHERE name == ? AND status != ?"; + constexpr const char *IS_TABLE_EXISTS = "SELECT * FROM sqlite_master WHERE type='table' AND name = ?"; /** @@ -499,6 +501,40 @@ namespace sqlite sqlite3_finalize(stmt); } + /** + * Populate the given instace ref with the container info of given name only if not destroyed. + * @param db Database connection. + * @param container_name Container name. + * @param instance Instance details. + * @return 0 on if exist otherwise -1. + */ + int get_instance(sqlite3 *db, std::string_view container_name, hp::instance_info &instance) + { + sqlite3_stmt *stmt; + std::string_view destroy_status(hp::CONTAINER_STATES[hp::STATES::DESTROYED]); + + if (sqlite3_prepare_v2(db, GET_INSTANCE, -1, &stmt, 0) == SQLITE_OK && stmt != NULL && + sqlite3_bind_text(stmt, 1, container_name.data(), container_name.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 2, destroy_status.data(), destroy_status.length(), SQLITE_STATIC) == SQLITE_OK && + (stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW)) + { + instance.container_name = reinterpret_cast(sqlite3_column_text(stmt, 0)); + instance.username = reinterpret_cast(sqlite3_column_text(stmt, 1)); + instance.assigned_ports.user_port = sqlite3_column_int64(stmt, 2); + instance.assigned_ports.peer_port = sqlite3_column_int64(stmt, 3); + instance.status = reinterpret_cast(sqlite3_column_text(stmt, 4)); + instance.image_name = reinterpret_cast(sqlite3_column_text(stmt, 5)); + + // Finalize and distroys the statement. + sqlite3_finalize(stmt); + return 0; + } + + // Finalize and distroys the statement. + sqlite3_finalize(stmt); + return -1; + } + /** * Get count of running instances * @param db Database connection. diff --git a/src/sqlite.hpp b/src/sqlite.hpp index a4b1262..5a6627e 100644 --- a/src/sqlite.hpp +++ b/src/sqlite.hpp @@ -77,6 +77,8 @@ namespace sqlite void get_instance_list(sqlite3 *db, std::vector &instances); + int get_instance(sqlite3 *db, std::string_view container_name, hp::instance_info &instance); + int get_allocated_instance_count(sqlite3 *db); } #endif