diff --git a/src/hp_manager.cpp b/src/hp_manager.cpp index 175dab9..7d759f7 100644 --- a/src/hp_manager.cpp +++ b/src/hp_manager.cpp @@ -7,12 +7,18 @@ namespace hp { // Keep track of the ports of the most recent hp instance. - uint16_t last_user_port, last_peer_port; + ports last_assigned_ports; + + // This is defaults to true because it initialize last assigned ports when a new instance is created if there is no vacant ports available. + bool last_port_assign_from_vacant = true; constexpr int FILE_PERMS = 0644; sqlite3 *db = NULL; // Database connection for hp related sqlite stuff. + // Vector keeping vacant ports from destroyed instances. + std::vector vacant_ports; + /** * Initialize hp related environment. */ @@ -25,12 +31,10 @@ namespace hp LOG_ERROR << "Error preparing hp database in " << db_path; return -1; } - const int peer_port_db = sqlite::get_max_port(db, "peer_port"); - last_peer_port = peer_port_db == 0 ? (conf::cfg.hp.init_peer_port -1) : peer_port_db; - const int user_port_db = sqlite::get_max_port(db, "user_port"); - last_user_port = user_port_db == 0 ? (conf::cfg.hp.init_user_port -1) : user_port_db; - + // Populate the vacant ports vector with vacant ports of destroyed containers. + sqlite::get_vacant_ports(db, vacant_ports); + return 0; } @@ -51,21 +55,37 @@ namespace hp */ int create_new_instance(instance_info &info, std::string_view owner_pubkey) { - const uint16_t user_port = last_user_port + 1; - const uint16_t peer_port = last_peer_port + 1; + ports instance_ports; + if (!vacant_ports.empty()) + { + // Assign a port pair from one of destroyed instances. + instance_ports = vacant_ports.back(); + last_port_assign_from_vacant = true; + } + else + { + if (last_port_assign_from_vacant) + { + sqlite::get_max_ports(db, last_assigned_ports); + last_port_assign_from_vacant = false; + } + instance_ports = {(uint16_t)(last_assigned_ports.peer_port + 1), (uint16_t)(last_assigned_ports.user_port + 1)}; + } const std::string name = crypto::generate_uuid(); // This will be the docker container name as well as the contract folder name. - - if (create_contract(info, name, peer_port, user_port) != 0 || - run_container(name, user_port, peer_port) != 0 || // Gives 3200 if docker failed. - sqlite::insert_hp_instance_row(db, owner_pubkey, info, CONTAINER_STATES[STATES::RUNNING]) == -1) + info.owner_pubkey = owner_pubkey; + if (create_contract(info, name, instance_ports) != 0 || + run_container(name, instance_ports) != 0 || // Gives 3200 if docker failed. + sqlite::insert_hp_instance_row(db, info) == -1) { LOG_ERROR << errno << ": Error creating and running new hp instance for " << owner_pubkey; return -1; } - last_user_port++; - last_peer_port++; + if (last_port_assign_from_vacant) + vacant_ports.pop_back(); + else + last_assigned_ports = instance_ports; return 0; } @@ -73,17 +93,16 @@ namespace hp /** * Runs a hotpocket docker image on the given contract and the ports. * @param folder_name Contract directory folder name. - * @param user_port User port to be assigned. - * @param peer_port Peer port to be assigned. + * @param assigned_ports Assigned ports to the container. * @return 0 on success execution or relavent error code on error. */ - int run_container(const std::string &folder_name, const uint16_t user_port, const uint16_t peer_port) + int run_container(const std::string &folder_name, const ports &assigned_ports) { const std::string command = "docker run -t -i -d --network=hpnet --stop-signal=SIGINT --name=" + folder_name + " \ -p " + - std::to_string(user_port) + ":" + std::to_string(user_port) + " \ + std::to_string(assigned_ports.user_port) + ":" + std::to_string(assigned_ports.user_port) + " \ -p " + - std::to_string(peer_port) + ":" + std::to_string(peer_port) + " \ + std::to_string(assigned_ports.peer_port) + ":" + std::to_string(assigned_ports.peer_port) + " \ --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined \ --mount type=bind,source=" + conf::cfg.hp.instance_folder + "/" + @@ -100,13 +119,14 @@ namespace hp */ int stop_container(const std::string &container_name) { - const int res = sqlite::is_container_exists_in_status(db, container_name, CONTAINER_STATES[STATES::RUNNING]); + instance_info info; + const int res = sqlite::is_container_exists(db, container_name, info); if (res == 0) { LOG_ERROR << "Given container not found. name: " << container_name; return -1; } - else if (res == 1) + else if (info.status != CONTAINER_STATES[STATES::RUNNING]) { LOG_ERROR << "Given container is not running. name: " << container_name; return -1; @@ -128,13 +148,14 @@ namespace hp */ int start_container(const std::string &container_name) { - const int res = sqlite::is_container_exists_in_status(db, container_name, CONTAINER_STATES[STATES::STOPPED]); + instance_info info; + const int res = sqlite::is_container_exists(db, container_name, info); if (res == 0) { LOG_ERROR << "Given container not found. name: " << container_name; return -1; } - else if (res == 1) + else if (info.status != CONTAINER_STATES[STATES::STOPPED]) { LOG_ERROR << "Given container is not stopped. name: " << container_name; return -1; @@ -156,7 +177,8 @@ namespace hp */ int destroy_container(const std::string &container_name) { - const int res = sqlite::is_container_exists_in_status(db, container_name, CONTAINER_STATES[STATES::STOPPED]); + instance_info info; + const int res = sqlite::is_container_exists(db, container_name, info); if (res == 0) { LOG_ERROR << "Given container not found. name: " << container_name; @@ -165,11 +187,16 @@ namespace hp const std::string command = "docker container rm -f " + container_name; const std::string folder_path = conf::cfg.hp.instance_folder + "/" + container_name; - if (system(command.c_str()) != 0 || sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::DESTROYED]) == -1 || util::remove_directory_recursively(folder_path) == -1) + if (system(command.c_str()) != 0 || + sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::DESTROYED]) == -1 || + util::remove_directory_recursively(folder_path) == -1) { LOG_ERROR << errno << ": Error destroying container " << container_name; return -1; } + // Add the port pair of the destroyed container to the vacant port vector. + if (std::find(vacant_ports.begin(), vacant_ports.end(), info.assigned_ports) == vacant_ports.end()) + vacant_ports.push_back(info.assigned_ports); return 0; } @@ -177,12 +204,11 @@ namespace hp * Creates a copy of default contract with the given name and the ports in the instance folder given in the config file. * @param info Information of the created contract instance. * @param folder_name Folder name for the contract directory. - * @param peer_port Peer port assigned to the hotpocket instance. - * @param user_port User port assigned to the hotpocket instance. + * @param assigned_ports Assigned ports to the instance. * @return -1 on error and 0 on success. * */ - int create_contract(instance_info &info, const std::string &folder_name, const uint16_t peer_port, const uint16_t user_port) + int create_contract(instance_info &info, const std::string &folder_name, const ports &assigned_ports) { const std::string folder_path = conf::cfg.hp.instance_folder + "/" + folder_name; const std::string command = "cp -r " + conf::ctx.default_contract_path + " " + folder_path; @@ -234,8 +260,8 @@ namespace hp jsoncons::ojson unl(jsoncons::json_array_arg); unl.push_back(util::to_hex(pubkey)); d["contract"]["unl"] = unl; - d["mesh"]["port"] = peer_port; - d["user"]["port"] = user_port; + d["mesh"]["port"] = assigned_ports.peer_port; + d["user"]["port"] = assigned_ports.user_port; if (write_json_file(config_fd, d) == -1) { @@ -249,8 +275,8 @@ namespace hp info.contract_id = contract_id; info.name = folder_name; info.pubkey = pubkey_hex; - info.user_port = user_port; - info.peer_port = peer_port; + info.assigned_ports = assigned_ports; + info.status = CONTAINER_STATES[STATES::RUNNING]; return 0; } diff --git a/src/hp_manager.hpp b/src/hp_manager.hpp index 1bc2a7d..f5ae174 100644 --- a/src/hp_manager.hpp +++ b/src/hp_manager.hpp @@ -6,6 +6,7 @@ namespace hp { constexpr const char *CONTAINER_STATES[]{"RUNNING", "STOPPED", "DESTROYED"}; + enum STATES { RUNNING, @@ -13,25 +14,39 @@ namespace hp DESTROYED }; + // Stores port pair assigned to a container. + struct ports + { + uint16_t peer_port = 0; + uint16_t user_port = 0; + + bool operator==(const ports &other) const + { + return peer_port == other.peer_port && user_port == other.user_port; + } + }; + struct instance_info { + std::string owner_pubkey; std::string name; std::string ip; std::string pubkey; std::string contract_id; - std::uint16_t peer_port; - std::uint16_t user_port; + ports assigned_ports; + std::string status; }; + int init(); void deinit(); int create_new_instance(instance_info &info, std::string_view owner_pubkey); - int run_container(const std::string &folder_name, const uint16_t user_port, const uint16_t peer_port); + int run_container(const std::string &folder_name, const ports &assigned_ports); int start_container(const std::string &container_name); int stop_container(const std::string &container_name); int destroy_container(const std::string &container_name); void kill_all_containers(); - int create_contract(instance_info &info, const std::string &folder_name, const uint16_t peer_port, const uint16_t user_port); + int create_contract(instance_info &info, const std::string &folder_name, const ports &assigned_ports); int write_json_file(const int fd, const jsoncons::ojson &d); } // 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 0e11c9b..a725cd1 100644 --- a/src/msg/json/msg_json.cpp +++ b/src/msg/json/msg_json.cpp @@ -295,11 +295,11 @@ namespace msg::json msg += SEP_COMMA; msg += "peer_port"; msg += SEP_COLON; - msg += std::to_string(info.peer_port); + msg += std::to_string(info.assigned_ports.peer_port); msg += SEP_COMMA; msg += "user_port"; msg += SEP_COLON; - msg += std::to_string(info.user_port); + msg += std::to_string(info.assigned_ports.user_port); msg += "\"}"; } diff --git a/src/sqlite.cpp b/src/sqlite.cpp index 7ba95ab..48a8af2 100644 --- a/src/sqlite.cpp +++ b/src/sqlite.cpp @@ -1,6 +1,7 @@ #include "sqlite.hpp" #include "salog.hpp" #include "util/util.hpp" +#include "conf.hpp" namespace sqlite { @@ -16,10 +17,6 @@ namespace sqlite constexpr const char *PRIMARY_KEY = "PRIMARY KEY"; constexpr const char *NOT_NULL = "NOT NULL"; constexpr const char *VALUES = "VALUES"; - constexpr const char *SELECT_ALL = "SELECT * FROM "; - constexpr const char *SQLITE_MASTER = "sqlite_master"; - constexpr const char *WHERE = " WHERE "; - constexpr const char *AND = " AND "; constexpr const char *INSTANCE_TABLE = "instances"; @@ -28,6 +25,18 @@ namespace sqlite "peer_port, user_port, pubkey, contract_id" ") VALUES(?,?,?,?,?,?,?,?,?)"; + constexpr const char *GET_VACANT_PORTS_FROM_HP = "SELECT DISTINCT peer_port, user_port FROM " + "instances WHERE status == ? AND user_port NOT IN" + "(SELECT user_port FROM instances WHERE status != ?)"; + + constexpr const char *GET_MAX_PORTS_FROM_HP = "SELECT max(peer_port), max(user_port) FROM instances WHERE status != ?"; + + constexpr const char *UPDATE_STATUS_IN_HP = "UPDATE instances SET status = ? WHERE name = ?"; + + constexpr const char *IS_CONTAINER_EXISTS = "SELECT * FROM instances WHERE name = ?"; + + constexpr const char *IS_TABLE_EXISTS = "SELECT * FROM sqlite_master WHERE type='table' AND name = ?"; + /** * Opens a connection to a given databse and give the db pointer. * @param db_name Database name to be connected. @@ -223,23 +232,11 @@ namespace sqlite */ bool is_table_exists(sqlite3 *db, std::string_view table_name) { - std::string sql; - // Reserving the space for the query before construction. - sql.reserve(sizeof(SELECT_ALL) + sizeof(SQLITE_MASTER) + sizeof(WHERE) + sizeof(AND) + table_name.size() + 19); - - sql.append(SELECT_ALL); - sql.append(SQLITE_MASTER); - sql.append(WHERE); - sql.append("type='table'"); - sql.append(AND); - sql.append("name='"); - sql.append(table_name); - sql.append("'"); - sqlite3_stmt *stmt; - if (sqlite3_prepare_v2(db, sql.data(), -1, &stmt, 0) == SQLITE_OK && - stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW) + if (sqlite3_prepare_v2(db, IS_TABLE_EXISTS, -1, &stmt, 0) == SQLITE_OK && + stmt != NULL && sqlite3_bind_text(stmt, 1, table_name.data(), table_name.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) { // Finalize and distroys the statement. sqlite3_finalize(stmt); @@ -303,20 +300,19 @@ namespace sqlite * Inserts a hp instance record. * @param db Pointer to the db. * @param info HP instance information. - * @param status Current status of the instance. * @returns returns 0 on success, or -1 on error. */ - int insert_hp_instance_row(sqlite3 *db, std::string_view owner_pubkey, const hp::instance_info &info, std::string_view status) + int insert_hp_instance_row(sqlite3 *db, const hp::instance_info &info) { sqlite3_stmt *stmt; if (sqlite3_prepare_v2(db, INSERT_INTO_HP_INSTANCE, -1, &stmt, 0) == SQLITE_OK && stmt != NULL && - sqlite3_bind_text(stmt, 1, owner_pubkey.data(), owner_pubkey.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 1, info.owner_pubkey.data(), info.owner_pubkey.length(), SQLITE_STATIC) == SQLITE_OK && sqlite3_bind_int64(stmt, 2, util::get_epoch_milliseconds()) == SQLITE_OK && - sqlite3_bind_text(stmt, 3, status.data(), status.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 3, info.status.data(), info.status.length(), SQLITE_STATIC) == SQLITE_OK && sqlite3_bind_text(stmt, 4, info.name.data(), info.name.length(), SQLITE_STATIC) == SQLITE_OK && sqlite3_bind_text(stmt, 5, info.ip.data(), info.ip.length(), SQLITE_STATIC) == SQLITE_OK && - sqlite3_bind_int64(stmt, 6, info.peer_port) == SQLITE_OK && - sqlite3_bind_int64(stmt, 7, info.user_port) == SQLITE_OK && + sqlite3_bind_int64(stmt, 6, info.assigned_ports.peer_port) == SQLITE_OK && + sqlite3_bind_int64(stmt, 7, info.assigned_ports.user_port) == SQLITE_OK && sqlite3_bind_text(stmt, 8, info.pubkey.data(), info.pubkey.length(), SQLITE_STATIC) == SQLITE_OK && sqlite3_bind_text(stmt, 9, info.contract_id.data(), info.contract_id.length(), SQLITE_STATIC) == SQLITE_OK && sqlite3_step(stmt) == SQLITE_DONE) @@ -330,45 +326,33 @@ namespace sqlite } /** - * Checks whether the container exist in the database and checks against the given status. + * Checks whether the container exist in the database and populate the instance information. * @param db Pointer to the db. * @param container_name Name of the container to be checked. - * @param status Status to check the container status against. - * @returns 0 if not found, 1 if container exists but not in given status and 2 if container exist in given status. + * @param info HP instance information. + * @returns 0 if not found, 1 if container exists . */ - int is_container_exists_in_status(sqlite3 *db, std::string_view container_name, std::string_view status) + int is_container_exists(sqlite3 *db, std::string_view container_name, hp::instance_info &info) { - std::string sql; - // Reserving the space for the query before construction. - sql.reserve(sizeof(SELECT_ALL) + sizeof(SQLITE_MASTER) + sizeof(WHERE) + sizeof(AND) + container_name.size() + 7); - - sql.append(SELECT_ALL); - sql.append(INSTANCE_TABLE); - sql.append(WHERE); - sql.append("name='"); - sql.append(container_name); - sql.append("'"); - sqlite3_stmt *stmt; - int result = 0; // Not exist. - if (sqlite3_prepare_v2(db, sql.data(), -1, &stmt, 0) == SQLITE_OK && - stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW) + if (sqlite3_prepare_v2(db, IS_CONTAINER_EXISTS, -1, &stmt, 0) == SQLITE_OK && + stmt != NULL && sqlite3_bind_text(stmt, 1, container_name.data(), container_name.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) { - const std::string current_status(reinterpret_cast(sqlite3_column_text(stmt, 2))); + // Populate only the necessary fields. + info.status = std::string(reinterpret_cast(sqlite3_column_text(stmt, 2))); + info.assigned_ports.peer_port = sqlite3_column_int64(stmt, 5); + info.assigned_ports.user_port = sqlite3_column_int64(stmt, 6); + // Finalize and distroys the statement. sqlite3_finalize(stmt); - if (current_status == status) - result = 2; - else - result = 1; - - return result; + return 1; } // Finalize and distroys the statement. sqlite3_finalize(stmt); - return result; + return 0; // Not found } /** @@ -380,55 +364,71 @@ namespace sqlite */ int update_status_in_container(sqlite3 *db, std::string_view container_name, std::string_view status) { - std::string sql; - // Reserving the space for the query before construction. - sql.reserve(sizeof(INSTANCE_TABLE) + status.length() + sizeof(WHERE) + container_name.size() + 30); - sql.append("UPDATE "); - sql.append(INSTANCE_TABLE); - sql.append(" SET status = '"); - sql.append(status); - sql.append("'"); - sql.append(WHERE); - sql.append("name='"); - sql.append(container_name); - sql.append("'"); - - return sqlite::exec_sql(db, sql); + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(db, UPDATE_STATUS_IN_HP, -1, &stmt, 0) == SQLITE_OK && stmt != NULL && + sqlite3_bind_text(stmt, 1, status.data(), 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 status for " << container_name; + return -1; } /** - * Get the max port already used for the instances. Ports used for already destroyed instances are excluded. + * Get the max peer and user ports assigned for instances excluding destroyed instances. * @param db Database connection. - * @param column_name Name of the column. Should be one of ['peer_port', 'user_port']. - * @return The port number. 0 is returned if no data found on database or on database error. + * @param max_ports Container holding max peer and user ports. */ - int get_max_port(sqlite3 *db, std::string_view column_name) + void get_max_ports(sqlite3 *db, hp::ports &max_ports) { - std::string sql; - // Reserving the space for the query before construction. - sql.reserve(sizeof(INSTANCE_TABLE) + column_name.length() + sizeof(WHERE) + sizeof(hp::CONTAINER_STATES[hp::STATES::DESTROYED]) + 29); - sql.append("SELECT max(") - .append(column_name) - .append(") from ") - .append(INSTANCE_TABLE) - .append(WHERE) - .append("status !='") - .append(hp::CONTAINER_STATES[hp::STATES::DESTROYED]) - .append("'"); - sqlite3_stmt *stmt; - if (sqlite3_prepare_v2(db, sql.data(), -1, &stmt, 0) == SQLITE_OK && - stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW) + if (sqlite3_prepare_v2(db, GET_MAX_PORTS_FROM_HP, -1, &stmt, 0) == SQLITE_OK && stmt != NULL && + sqlite3_bind_text(stmt, 1, hp::CONTAINER_STATES[hp::STATES::DESTROYED], sizeof(hp::CONTAINER_STATES[hp::STATES::DESTROYED]), SQLITE_STATIC) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) { - const int result = sqlite3_column_int64(stmt, 0); - // Finalize and distroys the statement. - sqlite3_finalize(stmt); - return result; + const uint16_t peer_port = sqlite3_column_int64(stmt, 0); + const uint16_t user_port = sqlite3_column_int64(stmt, 1); + + max_ports = {peer_port, user_port}; + } + // Initialize with default config values if either of the ports are zero. + if (max_ports.peer_port == 0 || max_ports.user_port == 0) + { + max_ports = {(uint16_t)(conf::cfg.hp.init_peer_port - 1), (uint16_t)(conf::cfg.hp.init_user_port - 1)}; + } + + // Finalize and distroys the statement. + sqlite3_finalize(stmt); + } + + /** + * Populate the given vector with vacant ports of destroyed instances which are not already assigned. + * @param db Database connection. + * @param vacant_ports Ports vector to hold port pairs from database. + */ + void get_vacant_ports(sqlite3 *db, std::vector &vacant_ports) + { + + sqlite3_stmt *stmt; + std::string_view destroy_status(hp::CONTAINER_STATES[hp::STATES::DESTROYED]); + + if (sqlite3_prepare_v2(db, GET_VACANT_PORTS_FROM_HP, -1, &stmt, 0) == SQLITE_OK && stmt != NULL && + sqlite3_bind_text(stmt, 1, destroy_status.data(), destroy_status.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 2, destroy_status.data(), destroy_status.length(), SQLITE_STATIC) == SQLITE_OK) + { + while (stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW) + { + const uint16_t peer_port = sqlite3_column_int64(stmt, 0); + const uint16_t user_port = sqlite3_column_int64(stmt, 1); + vacant_ports.push_back({peer_port, user_port}); + } } // Finalize and distroys the statement. sqlite3_finalize(stmt); - return 0; } } diff --git a/src/sqlite.hpp b/src/sqlite.hpp index 1ad5840..55e0262 100644 --- a/src/sqlite.hpp +++ b/src/sqlite.hpp @@ -63,12 +63,14 @@ namespace sqlite int initialize_hp_db(sqlite3 *db); - int insert_hp_instance_row(sqlite3 *db, std::string_view owner_pubkey, const hp::instance_info &info, std::string_view status); + int insert_hp_instance_row(sqlite3 *db, const hp::instance_info &info); - int is_container_exists_in_status(sqlite3 *db, std::string_view name, std::string_view status); + int is_container_exists(sqlite3 *db, std::string_view container_name, hp::instance_info &info); int update_status_in_container(sqlite3 *db, std::string_view container_name, std::string_view status); - int get_max_port(sqlite3 *db, std::string_view column_name); + void get_max_ports(sqlite3 *db, hp::ports &max_ports); + + void get_vacant_ports(sqlite3 *db, std::vector &vacant_ports); } #endif