From 43cb7e8ca10a64f66d830732bf627c8d7f5fbda2 Mon Sep 17 00:00:00 2001 From: Chalith Desaman Date: Tue, 29 Jun 2021 16:09:04 +0530 Subject: [PATCH] Two step instance creation and refactoring (#20) --- bootstrap-contract/bootstrap_contract.cpp | 4 +- bootstrap-contract/script.sh | 167 ++++++------ examples/message-board/message-board.js | 41 ++- src/comm/comm_session.cpp | 79 +++--- src/conf.cpp | 9 + src/conf.hpp | 1 + src/hp_manager.cpp | 299 +++++++++++++--------- src/hp_manager.hpp | 33 ++- src/msg/json/msg_json.cpp | 216 +++++++++++++--- src/msg/json/msg_json.hpp | 10 +- src/msg/msg_common.hpp | 25 ++ src/msg/msg_parser.cpp | 17 +- src/msg/msg_parser.hpp | 7 +- src/pchheader.hpp | 1 + src/util/util.cpp | 83 ++++++ src/util/util.hpp | 6 + 16 files changed, 712 insertions(+), 286 deletions(-) diff --git a/bootstrap-contract/bootstrap_contract.cpp b/bootstrap-contract/bootstrap_contract.cpp index 0c7aebc..53828d5 100644 --- a/bootstrap-contract/bootstrap_contract.cpp +++ b/bootstrap-contract/bootstrap_contract.cpp @@ -59,7 +59,7 @@ int main(int argc, char **argv) if (open(file_name.data(), O_CREAT | O_TRUNC | O_RDWR, 0644) == -1 || write(archive_fd, data.begin(), data.size()) == -1) { - std::cerr << "Error saving given file.\n"; + std::cerr << errno << ": Error saving given file.\n"; close(archive_fd); HP_DEINIT; return -1; @@ -74,7 +74,7 @@ int main(int argc, char **argv) const mode_t permission_mode = strtol(mode, 0, 8); // Char to octal conversion. if (chmod(HP_POST_EXEC_SCRIPT_NAME, permission_mode) < 0) { - std::cerr << "Chmod failed for " << HP_POST_EXEC_SCRIPT_NAME << std::endl; + std::cerr << errno << ": Chmod failed for " << HP_POST_EXEC_SCRIPT_NAME << std::endl; HP_DEINIT; return -1; } diff --git a/bootstrap-contract/script.sh b/bootstrap-contract/script.sh index b823b5e..4e104ac 100755 --- a/bootstrap-contract/script.sh +++ b/bootstrap-contract/script.sh @@ -1,6 +1,5 @@ #!/bin/bash -echo "Invoked seqence number: $1" -echo "Invoked lcl: $2" +echo "Execution lcl $1-$2" archive_name="bundle.zip" boostrap_bin="bootstrap_contract" install_script="install.sh" @@ -23,99 +22,93 @@ fi unzip -o $archive_name >>/dev/null # Verify necessary files in the archive. -if [ ! -f "$install_script" ] || [ ! -f "$contract_config" ]; then - echo "Required $install_script or $contract_config not found. Exiting.." +if [ ! -f "$install_script" ]; then + echo "Required $install_script not found. Exiting.." exit 1 fi -# jq command is used for json manipulation. -if ! command -v jq &>/dev/null; then - echo "jq utility not found. Installing.." - apt-get install -y jq >/dev/null 2>&1 -fi +if [ -f "$contract_config" ]; then + # jq command is used for json manipulation. + if ! command -v jq &>/dev/null; then + echo "jq utility not found. Installing.." + apt-get install -y jq >/dev/null 2>&1 + fi -# ********Config check******** -version=$(jq '.version' $contract_config) -if [ "$version" == "null" ] || [ ${#version} -eq 2 ]; then # Empty means "" - echo "Version cannot be empty" - exit 1 -fi + # ********Config check******** + version=$(jq '.version' $contract_config) + if [ "$version" == "null" ] || [ ${#version} -eq 2 ]; then # Empty means "" + echo "Version cannot be empty" + exit 1 + fi -unl=$(jq '.unl' $contract_config) -unl_res=$(jq '.unl? | map(length == 66 and startswith("ed")) | index(false)' $contract_config) -if [ "$unl_res" != "null" ]; then - echo "Unl pubkey invalid. Invalid format. Key should be 66 in length with ed prefix" - exit 1 -fi + unl=$(jq '.unl' $contract_config) + unl_res=$(jq '.unl? | map(length == 66 and startswith("ed")) | index(false)' $contract_config) + if [ "$unl_res" != "null" ]; then + echo "Unl pubkey invalid. Invalid format. Key should be 66 in length with ed prefix" + exit 1 + fi -bin_path=$(jq '.bin_path' $contract_config) -if [ "$bin_path" == "null" ] || [ ${#bin_path} -eq 2 ]; then # Empty means "" - echo "bin_path cannot be empty" - exit 1 -fi + bin_path=$(jq '.bin_path' $contract_config) + if [ "$bin_path" == "null" ] || [ ${#bin_path} -eq 2 ]; then # Empty means "" + echo "bin_path cannot be empty" + exit 1 + fi -if [ ! -f "${bin_path:1:-1}" ]; then - echo "Given binary file: $bin_path not found" - exit 1 -fi + if [ ! -f "${bin_path:1:-1}" ]; then + echo "Given binary file: $bin_path not found" + exit 1 + fi -bin_args=$(jq '.bin_args' $contract_config) + bin_args=$(jq '.bin_args' $contract_config) -roundtime=$(jq '.roundtime' $contract_config) -if [ "$roundtime" -le 0 ] || [ "$roundtime" -gt 3600000 ]; then - echo "Round time must be between 1 and 3600000ms inclusive." - exit 1 -fi + roundtime=$(jq '.roundtime' $contract_config) + if [ "$roundtime" -le 0 ] || [ "$roundtime" -gt 3600000 ]; then + echo "Round time must be between 1 and 3600000ms inclusive." + exit 1 + fi -stage_slice=$(jq '.stage_slice' $contract_config) -if [ "$stage_slice" -le 0 ] || [ "$stage_slice" -gt 33 ]; then - echo "Stage slice must be between 1 and 33 percent inclusive." - exit 1 -fi + stage_slice=$(jq '.stage_slice' $contract_config) + if [ "$stage_slice" -le 0 ] || [ "$stage_slice" -gt 33 ]; then + echo "Stage slice must be between 1 and 33 percent inclusive." + exit 1 + fi -consensus=$(jq '.consensus' $contract_config) -if [ "$consensus" == "null" ] || [ ${#consensus} -eq 2 ] || { [ "$consensus" != "\"public\"" ] && [ "$consensus" != "\"private\"" ]; }; then - echo "Invalid consensus flag. Valid values: public|private." - exit 1 -fi + consensus=$(jq '.consensus' $contract_config) + if [ "$consensus" == "null" ] || [ ${#consensus} -eq 2 ] || { [ "$consensus" != "\"public\"" ] && [ "$consensus" != "\"private\"" ]; }; then + echo "Invalid consensus flag. Valid values: public|private." + exit 1 + fi -npl=$(jq '.npl' $contract_config) -if [ "$npl" == "null" ] || [ ${#npl} -eq 2 ] || { [ "$npl" != "\"public\"" ] && [ "$npl" != "\"private\"" ]; }; then - echo "Invalid npl flag. Valid values: public|private." - exit 1 -fi + npl=$(jq '.npl' $contract_config) + if [ "$npl" == "null" ] || [ ${#npl} -eq 2 ] || { [ "$npl" != "\"public\"" ] && [ "$npl" != "\"private\"" ]; }; then + echo "Invalid npl flag. Valid values: public|private." + exit 1 + fi -max_input_ledger_offset=$(jq '.max_input_ledger_offset' $contract_config) -if [ "$max_input_ledger_offset" -lt 0 ]; then - echo "Invalid max input ledger offset. Should be greater than zero." - exit 1 -fi + max_input_ledger_offset=$(jq '.max_input_ledger_offset' $contract_config) + if [ "$max_input_ledger_offset" -lt 0 ]; then + echo "Invalid max input ledger offset. Should be greater than zero." + exit 1 + fi -appbill_mode=$(jq '.appbill.mode' $contract_config) -appbill_bin_args=$(jq '.appbill.bin_args' $contract_config) -r_user_input_bytes=$(jq '.round_limits.user_input_bytes' $contract_config) -r_user_output_bytes=$(jq '.round_limits.user_output_bytes' $contract_config) -r_npl_output_bytes=$(jq '.round_limits.npl_output_bytes' $contract_config) -r_proc_cpu_seconds=$(jq '.round_limits.proc_cpu_seconds' $contract_config) -r_proc_mem_bytes=$(jq '.round_limits.proc_mem_bytes' $contract_config) -r_proc_ofd_count=$(jq '.round_limits.proc_ofd_count' $contract_config) -if [ "$r_user_input_bytes" -lt 0 ] || [ "$r_user_output_bytes" -lt 0 ] || [ "$r_npl_output_bytes" -lt 0 ] || - [ "$r_proc_cpu_seconds" -lt 0 ] || [ "$r_proc_mem_bytes" -lt 0 ] || [ "$r_proc_ofd_count" -lt 0 ]; then - echo "Invalid round limits." - exit 1 -fi -echo "All $contract_config checks passed." + appbill_mode=$(jq '.appbill.mode' $contract_config) + appbill_bin_args=$(jq '.appbill.bin_args' $contract_config) + r_user_input_bytes=$(jq '.round_limits.user_input_bytes' $contract_config) + r_user_output_bytes=$(jq '.round_limits.user_output_bytes' $contract_config) + r_npl_output_bytes=$(jq '.round_limits.npl_output_bytes' $contract_config) + r_proc_cpu_seconds=$(jq '.round_limits.proc_cpu_seconds' $contract_config) + r_proc_mem_bytes=$(jq '.round_limits.proc_mem_bytes' $contract_config) + r_proc_ofd_count=$(jq '.round_limits.proc_ofd_count' $contract_config) + if [ "$r_user_input_bytes" -lt 0 ] || [ "$r_user_output_bytes" -lt 0 ] || [ "$r_npl_output_bytes" -lt 0 ] || + [ "$r_proc_cpu_seconds" -lt 0 ] || [ "$r_proc_mem_bytes" -lt 0 ] || [ "$r_proc_ofd_count" -lt 0 ]; then + echo "Invalid round limits." + exit 1 + fi + echo "All $contract_config checks passed." -# *****Install Script*****. -# Executing permissions. -chmod +x $install_script -# Executing install script -./$install_script - -echo "patch config" -patch="../patch.cfg" -# add the unl to below modification. removed for easy testing -new_patch=$(jq -M ". + {\ + echo "Updating patch.cfg file." + patch="../patch.cfg" + new_patch=$(jq -M ". + {\ version:$version,\ bin_path:$bin_path,\ bin_args:$bin_args,\ @@ -133,8 +126,18 @@ new_patch=$(jq -M ". + {\ proc_mem_bytes: $r_proc_mem_bytes,\ proc_ofd_count: $r_proc_ofd_count} }" $patch) -echo "$new_patch" >>tmp.cfg && mv tmp.cfg $patch + echo "$new_patch" >>tmp.cfg && mv tmp.cfg $patch + + # Remove contract.config after patch file update. + rm $contract_config +fi + +# *****Install Script*****. +# Executing permissions. +chmod +x $install_script +# Executing install script +./$install_script # Do the cleanups -rm $archive_name $install_script $contract_config $boostrap_bin +rm $archive_name $install_script $boostrap_bin exit 0 diff --git a/examples/message-board/message-board.js b/examples/message-board/message-board.js index da0ef2c..4549cde 100644 --- a/examples/message-board/message-board.js +++ b/examples/message-board/message-board.js @@ -6,8 +6,7 @@ const { v4: uuidv4 } = require('uuid'); const { execSync } = require("child_process"); // Generate tls keys if not found. -if (!fs.existsSync('./tlskey.pem')) -{ +if (!fs.existsSync('./tlskey.pem')) { console.log("TLS key files not detected. Generating.."); execSync("openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout tlskey.pem -out tlscert.pem -subj \"/C=SA/ST=SA/L=SA/O=SA/CN=SA\""); console.log("New tls key files generated.") @@ -76,6 +75,42 @@ server.listen(8080, () => { contract_id: contractId })); break; + case 'initiate': + containerName = await askForInput('Container Name'); + role = await askForInput('Role or '); + if (role && role != 'validator' && role != 'observer') { + console.error('Invalid role. (Should be "validator" or "observer").') + break; + } + history = await askForInput('History '); + split = []; + if (history) + { + split = history.split(','); + if (split.length == 0 || split.length == 0 > 3) { + console.error('Invalid history.') + break; + } + else if (split[0] != 'full' && split[0] != 'custom') { + console.error('Invalid history. (Should be "full" or "custom").') + break; + } + } + peers = await askForInput('Comma seperated Peer List ,,...'); + unl = await askForInput('Comma seperated UNL ,,...'); + sendToAll(JSON.stringify({ + id: uuidv4(), + type: 'initiate', + owner_pubkey: 'ed5cb83404120ac759609819591ef839b7d222c84f1f08b3012f490586159d2b50', + container_name: containerName, + peers: peers ? peers.split(',') : [], + unl: unl ? unl.split(',') : [], + role: role, + history: split.length > 0 ? split[0] : '', + max_primary_shards: split.length > 1 ? parseInt(split[1]) : '', + max_raw_shards: split.length > 2 ? parseInt(split[2]) : '' + })); + break; case 'destroy': containerName = await askForInput('Container Name'); sendToAll(JSON.stringify({ @@ -105,7 +140,7 @@ server.listen(8080, () => { break; default: - console.log('Invalid command. Only valid [create, destroy, start and stop]'); + console.error('Invalid command. Only valid [create, initiate, destroy, start and stop]'); break; } diff --git a/src/comm/comm_session.cpp b/src/comm/comm_session.cpp index d7d352b..1d8325b 100644 --- a/src/comm/comm_session.cpp +++ b/src/comm/comm_session.cpp @@ -2,6 +2,14 @@ #include "../util/util.hpp" #include "../hp_manager.hpp" +#define __HANDLE_RESPONSE(id, type, content, ret) \ + { \ + std::string res; \ + msg_parser.build_response(res, type, id, content, type == msg::MSGTYPE_CREATE_RES); \ + send(res); \ + return ret; \ + } + namespace comm { constexpr uint16_t MAX_IN_MSG_QUEUE_SIZE = 64; // Maximum in message queue size, The size passed is rounded to next number in binary sequence 1(1),11(3),111(7),1111(15),11111(31).... @@ -162,69 +170,70 @@ namespace comm */ int comm_session::handle_message(std::string_view msg) { - std::string type; - std::string id; - if (msg_parser.parse(msg) == -1 || msg_parser.extract_type(type) == -1) - return -1; + std::string id, type; + if (msg_parser.parse(msg) == -1 || msg_parser.extract_type_and_id(type, id) == -1) + __HANDLE_RESPONSE("", "error", "Invalid message.", -1); if (type == msg::MSGTYPE_CREATE) { msg::create_msg msg; if (msg_parser.extract_create_message(msg) == -1) - return -1; - id = msg.id; + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_CREATE_RES, "Invalid message.", -1); + hp::instance_info info; if (hp::create_new_instance(info, msg.pubkey, msg.contract_id) == -1) - return -1; + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_CREATE_RES, "Error creating instance.", -1); - std::string res; - msg_parser.build_create_response(res, info, msg.id); - send(res); + std::string create_res; + msg_parser.build_create_response(create_res, info); + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_CREATE_RES, create_res, 0); + } + else if (type == msg::MSGTYPE_INITIATE) + { + msg::initiate_msg msg; + if (msg_parser.extract_initiate_message(msg) == -1) + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_INITIATE_RES, "Invalid message.", -1); + + if (hp::initiate_instance(msg.container_name, msg) == -1) + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_INITIATE_RES, "Error initiating instance.", -1); + + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_INITIATE_RES, "Initiated", 0); } else if (type == msg::MSGTYPE_DESTROY) { msg::destroy_msg msg; if (msg_parser.extract_destroy_message(msg)) - return -1; - id = msg.id; - if (hp::destroy_container(msg.container_name) == -1) - return -1; + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_DESTROY_RES, "Invalid message.", -1); - std::string res; - msg_parser.build_response(res, msg::MSGTYPE_DESTROY_RES, msg.id, "Destroyed"); - send(res); + if (hp::destroy_container(msg.container_name) == -1) + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_DESTROY_RES, "Error destroying instance.", -1); + + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_DESTROY_RES, "Destroyed", 0); } else if (type == msg::MSGTYPE_START) { msg::start_msg msg; if (msg_parser.extract_start_message(msg)) - return -1; - id = msg.id; - if (hp::start_container(msg.container_name) == -1) - return -1; + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_START_RES, "Invalid message.", -1); - std::string res; - msg_parser.build_response(res, msg::MSGTYPE_START_RES, msg.id, "Started"); - send(res); + if (hp::start_container(msg.container_name) == -1) + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_START_RES, "Error starting instance.", -1); + + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_START_RES, "Started", 0); } else if (type == msg::MSGTYPE_STOP) { msg::stop_msg msg; if (msg_parser.extract_stop_message(msg)) - return -1; - id = msg.id; - if (hp::stop_container(msg.container_name) == -1) - return -1; + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_STOP_RES, "Invalid message.", -1); - std::string res; - msg_parser.build_response(res, msg::MSGTYPE_STOP_RES, msg.id, "Stopped"); - send(res); + if (hp::stop_container(msg.container_name) == -1) + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_STOP_RES, "Error stopping instance.", -1); + + __HANDLE_RESPONSE(msg.id, msg::MSGTYPE_STOP_RES, "Stopped", 0); } else - { - LOG_ERROR << "Received invalid message type."; - return -1; - } + __HANDLE_RESPONSE(id, "error", "Invalid message type.", -1); return 0; } diff --git a/src/conf.cpp b/src/conf.cpp index 4d2ed93..62b8172 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -202,6 +202,14 @@ namespace conf try { const jsoncons::ojson &hp = d["hp"]; + + cfg.hp.host_address = hp["host_address"].as(); + + if (cfg.hp.host_address.empty()) + { + std::cerr << "Configured hp host_address is empty.\n"; + return -1; + } cfg.hp.init_peer_port = hp["init_peer_port"].as(); if (cfg.hp.init_peer_port <= 1024) @@ -314,6 +322,7 @@ namespace conf // Hp configs. { jsoncons::ojson hp_config; + hp_config.insert_or_assign("host_address", cfg.hp.host_address); hp_config.insert_or_assign("init_peer_port", cfg.hp.init_peer_port); hp_config.insert_or_assign("init_user_port", cfg.hp.init_user_port); diff --git a/src/conf.hpp b/src/conf.hpp index f12a2ed..a9cef20 100644 --- a/src/conf.hpp +++ b/src/conf.hpp @@ -56,6 +56,7 @@ namespace conf struct hp_config { + std::string host_address; uint16_t init_peer_port; uint16_t init_user_port; }; diff --git a/src/hp_manager.cpp b/src/hp_manager.cpp index 8005c55..04856e5 100644 --- a/src/hp_manager.cpp +++ b/src/hp_manager.cpp @@ -1,5 +1,4 @@ #include "hp_manager.hpp" -#include "conf.hpp" #include "crypto.hpp" #include "util/util.hpp" #include "sqlite.hpp" @@ -26,7 +25,7 @@ namespace hp bool is_shutting_down = false; // We instruct the demon to restart the container automatically once the container exits except manually stopping. - constexpr const char *DOCKER_RUN = "DOCKER_HOST=unix:///run/user/$(id -u %s)/docker.sock /usr/bin/sashimono-agent/dockerbin/docker run -t -i -d --stop-signal=SIGINT --name=%s -p %s:%s -p %s:%s \ + constexpr const char *DOCKER_CREATE = "DOCKER_HOST=unix:///run/user/$(id -u %s)/docker.sock /usr/bin/sashimono-agent/dockerbin/docker create -t -i --stop-signal=SIGINT --name=%s -p %s:%s -p %s:%s \ --restart unless-stopped --mount type=bind,source=%s,target=/contract hotpocketdev/hotpocket:ubt.20.04 run /contract"; constexpr const char *DOCKER_START = "DOCKER_HOST=unix:///run/user/$(id -u %s)/docker.sock /usr/bin/sashimono-agent/dockerbin/docker start %s"; constexpr const char *DOCKER_STOP = "DOCKER_HOST=unix:///run/user/$(id -u %s)/docker.sock /usr/bin/sashimono-agent/dockerbin/docker stop %s"; @@ -128,7 +127,7 @@ namespace hp } /** - * Create a new instance of hotpocket. A new contract is created and then the docker images is run on that. + * Create a new instance of hotpocket. A new contract is created with docker image. * @param info Structure holding the generated instance info. * @param owner_pubkey Public key of the instance owner. * @param contract_id Contract id to be configured. @@ -170,11 +169,8 @@ namespace hp const std::string container_name = crypto::generate_uuid(); // This will be the docker container name as well as the contract folder name. const std::string contract_dir = util::get_user_contract_dir(username, container_name); - std::string hpfs_log_level; - bool is_full_history; if (create_contract(username, owner_pubkey, contract_id, contract_dir, instance_ports, info) == -1 || - read_contract_cfg_values(contract_dir, hpfs_log_level, is_full_history) == -1 || - hpfs::start_fs_processes(username, contract_dir, hpfs_log_level, is_full_history) == -1) + create_container(username, container_name, contract_dir, instance_ports, info) == -1) { LOG_ERROR << errno << ": Error creating hp instance for " << owner_pubkey; // Remove user if instance creation failed. @@ -182,12 +178,11 @@ namespace hp return -1; } - if (run_container(username, container_name, contract_dir, instance_ports, info) == -1 || - sqlite::insert_hp_instance_row(db, info) == -1) + if (sqlite::insert_hp_instance_row(db, info) == -1) { - LOG_ERROR << errno << ": Error running new hp instance for " << owner_pubkey; - // Stop started hpfs processes and remove user if running instance failed. - hpfs::stop_fs_processes(username); + LOG_ERROR << errno << ": Error creating hp instance for " << owner_pubkey; + // Remove container and uninstall user if database update failed. + docker_remove(username, container_name); uninstall_user(username); return -1; } @@ -201,20 +196,87 @@ namespace hp } /** - * Runs a hotpocket docker image on the given contract and the ports. + * Initiate the instance. The config will be updated and container will be started. + * @param container_name Name of the container. + * @param config_msg Config values for the hp instance. + * @return 0 on success and -1 on error. + */ + int initiate_instance(std::string_view container_name, const msg::initiate_msg &config_msg) + { + 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 (info.status != CONTAINER_STATES[STATES::CREATED]) + { + LOG_ERROR << "Given container is already initiated. name: " << container_name; + return -1; + } + + // Read the config file into json document object. + const std::string contract_dir = util::get_user_contract_dir(info.username, container_name); + std::string config_file_path(contract_dir); + config_file_path.append("/cfg/hp.cfg"); + const int config_fd = open(config_file_path.data(), O_RDWR, FILE_PERMS); + if (config_fd == -1) + { + LOG_ERROR << errno << ": Error opening hp config file " << config_file_path; + return -1; + } + + jsoncons::ojson d; + std::string hpfs_log_level; + bool is_full_history; + if (util::read_json_file(config_fd, d) == -1 || + write_json_values(d, config_msg) == -1 || + read_json_values(d, hpfs_log_level, is_full_history) == -1 || + util::write_json_file(config_fd, d) == -1 || + hpfs::start_fs_processes(info.username, contract_dir, hpfs_log_level, is_full_history) == -1) + { + LOG_ERROR << "Error when setting up container. name: " << container_name; + close(config_fd); + return -1; + } + close(config_fd); + + if (docker_start(info.username, container_name) == -1) + { + LOG_ERROR << "Error when starting container. name: " << container_name; + // Stop started hpfs processes if starting instance failed. + hpfs::stop_fs_processes(info.username); + return -1; + } + + if (sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::RUNNING]) == -1) + { + LOG_ERROR << "Error when starting container. name: " << container_name; + // Stop started docker and hpfs processes if database update fails. + docker_stop(info.username, container_name); + hpfs::stop_fs_processes(info.username); + return -1; + } + + return 0; + } + + /** + * Creates a hotpocket docker image on the given contract and the ports. * @param username Username of the instance user. * @param container_name Name of the container. * @param contract_dir Directory for the contract. * @param assigned_ports Assigned ports to the container. * @return 0 on success execution or relavent error code on error. */ - int run_container(std::string_view username, std::string_view container_name, std::string_view contract_dir, const ports &assigned_ports, instance_info &info) + int create_container(std::string_view username, std::string_view container_name, std::string_view contract_dir, const ports &assigned_ports, instance_info &info) { const std::string user_port = std::to_string(assigned_ports.user_port); const std::string peer_port = std::to_string(assigned_ports.peer_port); const int len = 303 + username.length() + container_name.length() + (user_port.length() * 2) + (peer_port.length() * 2) + contract_dir.length(); char command[len]; - sprintf(command, DOCKER_RUN, username.data(), container_name.data(), user_port.data(), user_port.data(), peer_port.data(), peer_port.data(), contract_dir.data()); + sprintf(command, DOCKER_CREATE, username.data(), container_name.data(), user_port.data(), user_port.data(), peer_port.data(), peer_port.data(), contract_dir.data()); if (system(command) != 0) { LOG_ERROR << "Error when running container. name: " << container_name; @@ -277,15 +339,29 @@ namespace hp return -1; } + // Read the config file into json document object. + const std::string contract_dir = util::get_user_contract_dir(info.username, container_name); + std::string config_file_path(contract_dir); + config_file_path.append("/cfg/hp.cfg"); + const int config_fd = open(config_file_path.data(), O_RDONLY); + if (config_fd == -1) + { + LOG_ERROR << errno << ": Error opening hp config file " << config_file_path; + return -1; + } + + jsoncons::ojson d; std::string hpfs_log_level; bool is_full_history; - const std::string contract_dir = util::get_user_contract_dir(info.username, container_name); - if (read_contract_cfg_values(contract_dir, hpfs_log_level, is_full_history) == -1 || + if (util::read_json_file(config_fd, d) == -1 || + read_json_values(d, hpfs_log_level, is_full_history) == -1 || hpfs::start_fs_processes(info.username, contract_dir, hpfs_log_level, is_full_history) == -1) { LOG_ERROR << "Error when setting up container. name: " << container_name; + close(config_fd); return -1; } + close(config_fd); if (docker_start(info.username, container_name) == -1) { @@ -329,13 +405,26 @@ namespace hp */ int docker_stop(std::string_view username, std::string_view container_name) { - const int len = 99 + username.length() + container_name.length(); char command[len]; sprintf(command, DOCKER_STOP, username.data(), container_name.data()); return system(command) == 0 ? 0 : -1; } + /** + * Execute docker rm command. + * @param username Username of the instance user. + * @param container_name Name of the container. + * @return 0 on successful execution and -1 on error. + */ + int docker_remove(std::string_view username, std::string_view container_name) + { + const int len = 100 + username.length() + container_name.length(); + char command[len]; + sprintf(command, DOCKER_REMOVE, username.data(), container_name.data()); + return system(command) == 0 ? 0 : -1; + } + /** * Destroy the container with given name if exists. * @param container_name Name of the container. @@ -351,11 +440,7 @@ namespace hp return -1; } - const int len = 100 + info.username.length() + container_name.length(); - char command[len]; - sprintf(command, DOCKER_REMOVE, info.username.data(), container_name.data()); - - if (system(command) != 0 || + if (docker_remove(info.username, container_name) == -1 || sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::DESTROYED]) == -1 || hpfs::stop_fs_processes(info.username) == -1) { @@ -391,8 +476,8 @@ namespace hp // Folders inside /tmp directory will be cleaned after a reboot. So this will self cleanup folders // that might be remaining due to another error in the workflow. char templ[17] = "/tmp/sashiXXXXXX"; - char *temp_foldername = mkdtemp(templ); - if (temp_foldername == NULL) + const char *temp_dirpath = mkdtemp(templ); + if (temp_dirpath == NULL) { LOG_ERROR << errno << ": Error creating temporary directory to create contract folder."; return -1; @@ -400,43 +485,30 @@ namespace hp const std::string source_path = conf::ctx.default_contract_path + "/*"; int len = 25 + source_path.length(); char cp_command[len]; - sprintf(cp_command, COPY_DIR, source_path.data(), temp_foldername); + sprintf(cp_command, COPY_DIR, source_path.data(), temp_dirpath); if (system(cp_command) != 0) { - LOG_ERROR << "Default contract copying failed to " << temp_foldername; + LOG_ERROR << "Default contract copying failed to " << temp_dirpath; return -1; } // Read the config file into json document object. - std::string config_file_path(temp_foldername); + std::string config_file_path(temp_dirpath); config_file_path.append("/cfg/hp.cfg"); const int config_fd = open(config_file_path.data(), O_RDWR, FILE_PERMS); + if (config_fd == -1) { LOG_ERROR << errno << ": Error opening hp config file " << config_file_path; return -1; } - std::string buf; - if (util::read_from_fd(config_fd, buf) == -1) - { - std::cerr << "Error reading from the config file. " << errno << '\n'; - close(config_fd); - return -1; - } - jsoncons::ojson d; - try + if (util::read_json_file(config_fd, d) == -1) { - d = jsoncons::ojson::parse(buf, jsoncons::strict_json_parsing()); - } - catch (const std::exception &e) - { - std::cerr << "Invalid config file format. " << e.what() << '\n'; close(config_fd); return -1; } - buf.clear(); std::string pubkey, seckey; crypto::generate_signing_keys(pubkey, seckey); @@ -463,7 +535,7 @@ namespace hp d["user"]["port"] = assigned_ports.user_port; d["hpfs"]["external"] = true; - if (write_json_file(config_fd, d) == -1) + if (util::write_json_file(config_fd, d) == -1) { LOG_ERROR << "Writing modified hp config failed."; close(config_fd); @@ -474,7 +546,7 @@ namespace hp // Move the contract to contract dir len = 22 + contract_dir.length(); char mv_command[len]; - sprintf(mv_command, MOVE_DIR, temp_foldername, contract_dir.data()); + sprintf(mv_command, MOVE_DIR, temp_dirpath, contract_dir.data()); if (system(mv_command) != 0) { LOG_ERROR << "Default contract moving failed to " << contract_dir; @@ -494,45 +566,11 @@ namespace hp info.owner_pubkey = owner_pubkey; info.username = username; info.contract_dir = contract_dir; - info.ip = "localhost"; + info.ip = conf::cfg.hp.host_address; info.contract_id = contract_id; info.pubkey = pubkey_hex; info.assigned_ports = assigned_ports; - info.status = CONTAINER_STATES[STATES::RUNNING]; - return 0; - } - - /** - * Writes the given json doc to a file. - * @param fd File descriptor to the open file. - * @param d A valid JSON document. - * @return 0 on success. -1 on failure. - */ - int write_json_file(const int fd, const jsoncons::ojson &d) - { - std::string json; - // Convert json object to a string. - try - { - jsoncons::json_options options; - options.object_array_line_splits(jsoncons::line_split_kind::multi_line); - options.spaces_around_comma(jsoncons::spaces_option::no_spaces); - std::ostringstream os; - os << jsoncons::pretty_print(d, options); - json = os.str(); - os.clear(); - } - catch (const std::exception &e) - { - LOG_ERROR << "Converting modified hp config json to string failed. "; - return -1; - } - - if (ftruncate(fd, 0) == -1 || write(fd, json.data(), json.size()) == -1) - { - LOG_ERROR << "Writing modified hp config file failed. "; - return -1; - } + info.status = CONTAINER_STATES[STATES::CREATED]; return 0; } @@ -571,46 +609,16 @@ namespace hp /** * Read only required contract config values - * @param contract_dir Directory of the contract. - * @param log_level Log level to be read. + * @param d Json file to be read. + * @param hpfs_log_level Hpfs log level. * @param is_full_history Contract history mode. * @return 0 on success. -1 on failure. */ - int read_contract_cfg_values(std::string_view contract_dir, std::string &log_level, bool &is_full_history) + int read_json_values(const jsoncons::ojson &d, std::string &hpfs_log_level, bool &is_full_history) { - // Read the config file into json document object. - std::string config_file_path(contract_dir); - config_file_path.append("/cfg/hp.cfg"); - const int config_fd = open(config_file_path.data(), O_RDONLY); - if (config_fd == -1) - { - LOG_ERROR << errno << ": Error opening hp config file " << config_file_path; - return -1; - } - - std::string buf; - if (util::read_from_fd(config_fd, buf) == -1) - { - LOG_ERROR << "Error reading from the config file. " << errno; - close(config_fd); - return -1; - } - - jsoncons::ojson d; try { - d = jsoncons::ojson::parse(buf, jsoncons::strict_json_parsing()); - } - catch (const std::exception &e) - { - LOG_ERROR << "Invalid contract config file format. " << e.what(); - return -1; - } - buf.clear(); - - try - { - log_level = d["hpfs"]["log"]["log_level"].as(); + hpfs_log_level = d["hpfs"]["log"]["log_level"].as(); } catch (const std::exception &e) { @@ -619,7 +627,7 @@ namespace hp } const std::unordered_set valid_loglevels({"dbg", "inf", "wrn", "err"}); - if (valid_loglevels.count(log_level) != 1) + if (valid_loglevels.count(hpfs_log_level) != 1) { LOG_ERROR << "Invalid hpfs loglevel configured. Valid values: dbg|inf|wrn|err"; return -1; @@ -646,6 +654,65 @@ namespace hp return 0; } + /** + * Write contract config values (only updated if provided config values are not empty) into the json file. + * @param d Json file to be populated. + * @param config_msg Config values to be updated. + * @return 0 on success. -1 on failure. + */ + int write_json_values(jsoncons::ojson &d, const msg::initiate_msg &config_msg) + { + if (!config_msg.unl.empty()) + { + jsoncons::ojson unl(jsoncons::json_array_arg); + for (auto &pubkey : config_msg.unl) + unl.push_back(util::to_hex(pubkey)); + d["contract"]["unl"] = unl; + } + + if (!config_msg.peers.empty()) + { + jsoncons::ojson peers(jsoncons::json_array_arg); + for (auto &peer : config_msg.peers) + peers.push_back(peer.host_address + ":" + std::to_string(peer.port)); + d["mesh"]["known_peers"] = peers; + } + + if (!config_msg.role.empty()) + { + if (config_msg.role != "observer" && config_msg.role != "validator") + { + LOG_ERROR << "Invalid role value observer|validator"; + return -1; + } + d["node"]["role"] = config_msg.role; + } + + if (!config_msg.history.empty()) + { + if (config_msg.history != "full" && config_msg.history != "custom") + { + LOG_ERROR << "Invalid history value full|custom"; + return -1; + } + d["node"]["history"] = config_msg.history; + } + + if (config_msg.max_primary_shards.has_value()) + d["node"]["history_config"]["max_primary_shards"] = config_msg.max_primary_shards.value(); + + if (config_msg.max_raw_shards.has_value()) + d["node"]["history_config"]["max_raw_shards"] = config_msg.max_raw_shards.value(); + + if (d["node"]["history"].as() == "custom" && d["node"]["history_config"]["max_primary_shards"].as() == 0) + { + LOG_ERROR << "'max_primary_shards' cannot be zero in history=custom mode."; + return -1; + } + + return 0; + } + /** * Executes the given bash file and populates final comma seperated output into a vector. * @param file_name Name of the bash script. diff --git a/src/hp_manager.hpp b/src/hp_manager.hpp index e281c24..a965d3c 100644 --- a/src/hp_manager.hpp +++ b/src/hp_manager.hpp @@ -3,13 +3,17 @@ #include "pchheader.hpp" #include "hpfs_manager.hpp" +#include "conf.hpp" +#include "conf.hpp" +#include "msg/msg_common.hpp" namespace hp { - constexpr const char *CONTAINER_STATES[]{"running", "stopped", "destroyed", "exited"}; + constexpr const char *CONTAINER_STATES[]{"created", "running", "stopped", "destroyed", "exited"}; enum STATES { + CREATED, RUNNING, STOPPED, DESTROYED, @@ -49,23 +53,42 @@ namespace hp }; int init(); + void deinit(); + void hp_monitor_loop(); + int create_new_instance(instance_info &info, std::string_view owner_pubkey, const std::string &contract_id); - int run_container(std::string_view username, std::string_view container_name, std::string_view contract_dir, const ports &assigned_ports, instance_info &info); + + int initiate_instance(std::string_view container_name, const msg::initiate_msg &config_msg); + + int create_container(std::string_view username, std::string_view container_name, std::string_view contract_dir, const ports &assigned_ports, instance_info &info); + int start_container(std::string_view container_name); + int docker_start(std::string_view username, std::string_view container_name); + int docker_stop(std::string_view username, std::string_view container_name); + + int docker_remove(std::string_view username, std::string_view container_name); + int stop_container(std::string_view container_name); + int destroy_container(std::string_view container_name); - void kill_all_containers(); + int create_contract(std::string_view username, std::string_view owner_pubkey, std::string_view contract_id, std::string_view contract_dir, const ports &assigned_ports, instance_info &info); - int write_json_file(const int fd, const jsoncons::ojson &d); + int check_instance_status(std::string_view username, std::string_view container_name, std::string &status); - int read_contract_cfg_values(std::string_view contract_dir, std::string &log_level, bool &is_full_history); + + int read_json_values(const jsoncons::ojson &d, std::string &hpfs_log_level, bool &is_full_history); + + int write_json_values(jsoncons::ojson &d, const msg::initiate_msg &config_msg); + int execute_bash_file(std::string_view file_name, std::vector &output_params, std::string_view input_param = {}); + int install_user(int &user_id, std::string &username); + int uninstall_user(std::string_view username); } // 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 c19542e..c249e0a 100644 --- a/src/msg/json/msg_json.cpp +++ b/src/msg/json/msg_json.cpp @@ -1,4 +1,5 @@ #include "msg_json.hpp" +#include "../../util/util.hpp" namespace msg::json { @@ -43,11 +44,36 @@ namespace msg::json } /** - * Extracts the message 'type' value from the json document. + * Extracts the message 'type' and 'id' values from the json document. */ - int extract_type(std::string &extracted_type, const jsoncons::json &d) + int extract_type_and_id(std::string &extracted_type, std::string &extracted_id, const jsoncons::json &d) { + if (!d.contains(msg::FLD_TYPE)) + { + LOG_ERROR << "Field type is missing."; + return -1; + } + + if (!d[msg::FLD_TYPE].is()) + { + LOG_ERROR << "Invalid type value."; + return -1; + } extracted_type = d[msg::FLD_TYPE].as(); + + if (!d.contains(msg::FLD_ID)) + { + LOG_ERROR << "Field id is missing."; + return -1; + } + + if (!d[msg::FLD_ID].is()) + { + LOG_ERROR << "Invalid id value."; + return -1; + } + extracted_id = d[msg::FLD_ID].as(); + return 0; } @@ -69,21 +95,9 @@ namespace msg::json */ int extract_commons(std::string &type, std::string &id, std::string &pubkey, const jsoncons::json &d) { - if (extract_type(type, d) == -1) + if (extract_type_and_id(type, id, d) == -1) return -1; - if (!d.contains(msg::FLD_ID)) - { - LOG_ERROR << "Field id is missing."; - return -1; - } - - if (!d[msg::FLD_ID].is()) - { - LOG_ERROR << "Invalid id value."; - return -1; - } - if (!d.contains(msg::FLD_PUBKEY)) { LOG_ERROR << "Field owner_pubkey is missing."; @@ -96,7 +110,6 @@ namespace msg::json return -1; } - id = d[msg::FLD_ID].as(); pubkey = d[msg::FLD_PUBKEY].as(); return 0; } @@ -134,6 +147,158 @@ namespace msg::json return 0; } + /** + * Extracts initiate message from msg. + * @param msg Populated msg object. + * @param d The json document holding the read request message. + * Accepted signed input container format: + * { + * "type": "initiate", + * "owner_pubkey": "", + * "container_name": "", + * "peers": [<'ip:port' peer list>], + * "unl": [], + * "role": , + * "history": , + * "max_primary_shards": , + * "max_raw_shards": + * } + * @return 0 on successful extraction. -1 for failure. + */ + int extract_initiate_message(initiate_msg &msg, const jsoncons::json &d) + { + if (extract_commons(msg.type, msg.id, msg.pubkey, d) == -1) + return -1; + + if (!d.contains(msg::FLD_CONTAINER_NAME)) + { + LOG_ERROR << "Field contract_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(); + + if (d.contains(msg::FLD_PEERS)) + { + if (!d[msg::FLD_PEERS].empty() && !d[msg::FLD_PEERS].is_array()) + { + LOG_ERROR << "Invalid peers value."; + return -1; + } + else if (!d[msg::FLD_PEERS].empty() && d[msg::FLD_PEERS].size() > 0) + { + std::vector splitted; + for (auto &val : d[msg::FLD_PEERS].array_range()) + { + if (!val.is()) + { + LOG_ERROR << "Invalid peer value."; + return -1; + } + + const std::string peer = val.as(); + util::split_string(splitted, peer, ":"); + if (splitted.size() != 2) + { + LOG_ERROR << "Invalid peer value: " << peer; + return -1; + } + + uint16_t port; + if (util::stoul(splitted.back(), port) == -1) + { + LOG_ERROR << "Invalid peer port value: " << peer; + return -1; + } + + msg.peers.emplace(conf::host_ip_port{splitted.front(), port}); + splitted.clear(); + } + } + } + + if (d.contains(msg::FLD_UNL)) + { + if (!d[msg::FLD_UNL].empty() && !d[msg::FLD_UNL].is_array()) + { + LOG_ERROR << "Invalid unl value."; + return -1; + } + else if (!d[msg::FLD_UNL].empty() && d[msg::FLD_UNL].size() > 0) + { + for (auto &val : d[msg::FLD_UNL].array_range()) + { + if (!val.is()) + { + LOG_ERROR << "Invalid unl pubkey value."; + return -1; + } + + const std::string unl_pubkey = val.as(); + const std::string unl_pubkey_bin = util::to_bin(unl_pubkey); + if (unl_pubkey_bin.empty()) + { + LOG_ERROR << "Invalid unl pubkey value: " << unl_pubkey; + return -1; + } + + msg.unl.emplace(unl_pubkey_bin); + } + } + } + + if (d.contains(msg::FLD_ROLE)) + { + if (!d[msg::FLD_ROLE].is()) + { + LOG_ERROR << "Invalid role value."; + return -1; + } + + msg.role = d[msg::FLD_ROLE].as(); + } + + if (d.contains(msg::FLD_HISTORY)) + { + if (!d[msg::FLD_HISTORY].is()) + { + LOG_ERROR << "Invalid history value."; + return -1; + } + + msg.history = d[msg::FLD_HISTORY].as(); + } + + if (d.contains(msg::FLD_MAX_P_SHARDS)) + { + if (!d[msg::FLD_MAX_P_SHARDS].empty() && !d[msg::FLD_MAX_P_SHARDS].is()) + { + LOG_ERROR << "Invalid max_primary_shards value."; + return -1; + } + else if (!d[msg::FLD_MAX_P_SHARDS].empty()) + msg.max_primary_shards = d[msg::FLD_MAX_P_SHARDS].as(); + } + + if (d.contains(msg::FLD_MAX_R_SHARDS)) + { + if (!d[msg::FLD_MAX_R_SHARDS].empty() && !d[msg::FLD_MAX_R_SHARDS].is()) + { + LOG_ERROR << "Invalid max_raw_shards value."; + return -1; + } + else if (!d[msg::FLD_MAX_R_SHARDS].empty()) + msg.max_raw_shards = d[msg::FLD_MAX_R_SHARDS].as(); + } + return 0; + } + /** * Extracts destroy message from msg. * @param msg Populated msg object. @@ -244,8 +409,9 @@ namespace msg::json * } * @param response_type Type of the response. * @param content Content inside the response. + * @param json_content Whether content is a json string. */ - void build_response(std::string &msg, std::string_view response_type, std::string_view reply_for, std::string_view content) + void build_response(std::string &msg, std::string_view response_type, std::string_view reply_for, std::string_view content, const bool json_content) { msg.reserve(1024); msg += "{\""; @@ -258,9 +424,9 @@ namespace msg::json msg += response_type; msg += SEP_COMMA; msg += msg::FLD_CONTENT; - msg += SEP_COLON; + msg += (json_content ? SEP_COLON_NOQUOTE : SEP_COLON); msg += content; - msg += "\"}"; + msg += (json_content ? "}" : "\"}"); } /** @@ -268,8 +434,6 @@ namespace msg::json * @param msg Buffer to construct the generated json message string into. * Message format: * { - * 'reply_for': '' - * 'type': '', * "name": "" * "username": """ * "ip": "" @@ -281,18 +445,10 @@ namespace msg::json * @param response_type Type of the response. * @param content Content inside the response. */ - void build_create_response(std::string &msg, const hp::instance_info &info, std::string_view reply_for) + void build_create_response(std::string &msg, const hp::instance_info &info) { msg.reserve(1024); msg += "{\""; - msg += msg::FLD_REPLY_FOR; - msg += SEP_COLON; - msg += std::string(reply_for); - msg += SEP_COMMA; - msg += msg::FLD_TYPE; - msg += SEP_COLON; - msg += msg::MSGTYPE_CREATE_RES; - msg += SEP_COMMA; msg += "name"; msg += SEP_COLON; msg += info.container_name; diff --git a/src/msg/json/msg_json.hpp b/src/msg/json/msg_json.hpp index 09cf634..5a73d43 100644 --- a/src/msg/json/msg_json.hpp +++ b/src/msg/json/msg_json.hpp @@ -12,21 +12,23 @@ namespace msg::json { int parse_message(jsoncons::json &d, std::string_view message); - int extract_type(std::string &extracted_type, const jsoncons::json &d); + int extract_type_and_id(std::string &extracted_type, std::string &extracted_id, const jsoncons::json &d); int extract_commons(std::string &type, std::string &id, std::string &pubkey, const jsoncons::json &d); int extract_create_message(create_msg &msg, const jsoncons::json &d); - + + int extract_initiate_message(initiate_msg &msg, const jsoncons::json &d); + int extract_destroy_message(destroy_msg &msg, const jsoncons::json &d); int extract_start_message(start_msg &msg, const jsoncons::json &d); int extract_stop_message(stop_msg &msg, const jsoncons::json &d); - void build_response(std::string &msg, std::string_view response_type, std::string_view reply_for, std::string_view content); + void build_response(std::string &msg, std::string_view response_type, std::string_view reply_for, std::string_view content, const bool json_content = false); - void build_create_response(std::string &msg, const hp::instance_info &info, std::string_view reply_for); + void build_create_response(std::string &msg, const hp::instance_info &info); } // namespace msg::json diff --git a/src/msg/msg_common.hpp b/src/msg/msg_common.hpp index fa3f1d4..3df1900 100644 --- a/src/msg/msg_common.hpp +++ b/src/msg/msg_common.hpp @@ -2,6 +2,7 @@ #define _HP_MSG_MSG_COMMON_ #include "../pchheader.hpp" +#include "../conf.hpp" namespace msg { @@ -13,6 +14,22 @@ namespace msg std::string contract_id; }; + // Keep numerical config valus as optional so when updating the config if the value is empty + // We do nothing otherwise we take the value and update the config. + struct initiate_msg + { + std::string id; + std::string type; + std::string pubkey; + std::string container_name; + std::set peers; + std::set unl; + std::string role; + std::string history; + std::optional max_primary_shards; + std::optional max_raw_shards; + }; + struct destroy_msg { std::string id; @@ -45,16 +62,24 @@ namespace msg constexpr const char *FLD_CONTAINER_NAME = "container_name"; constexpr const char *FLD_CONTRACT_ID = "contract_id"; constexpr const char *FLD_ID = "id"; + constexpr const char *FLD_PEERS = "peers"; + constexpr const char *FLD_UNL = "unl"; + constexpr const char *FLD_ROLE = "role"; + constexpr const char *FLD_HISTORY = "history"; + constexpr const char *FLD_MAX_P_SHARDS = "max_primary_shards"; + constexpr const char *FLD_MAX_R_SHARDS = "max_raw_shards"; // Message types constexpr const char *MSGTYPE_INIT = "init"; constexpr const char *MSGTYPE_CREATE = "create"; + constexpr const char *MSGTYPE_INITIATE = "initiate"; constexpr const char *MSGTYPE_DESTROY = "destroy"; constexpr const char *MSGTYPE_START = "start"; constexpr const char *MSGTYPE_STOP = "stop"; // Message res types constexpr const char *MSGTYPE_CREATE_RES = "create_res"; + constexpr const char *MSGTYPE_INITIATE_RES = "initiate_res"; constexpr const char *MSGTYPE_DESTROY_RES = "destroy_res"; constexpr const char *MSGTYPE_START_RES = "start_res"; constexpr const char *MSGTYPE_STOP_RES = "stop_res"; diff --git a/src/msg/msg_parser.cpp b/src/msg/msg_parser.cpp index dc28125..2bf5bf1 100644 --- a/src/msg/msg_parser.cpp +++ b/src/msg/msg_parser.cpp @@ -8,15 +8,20 @@ namespace msg return json::parse_message(jdoc, message); } - int msg_parser::extract_type(std::string &extracted_type) const + int msg_parser::extract_type_and_id(std::string &extracted_type, std::string &extracted_id) const { - return json::extract_type(extracted_type, jdoc); + return json::extract_type_and_id(extracted_type, extracted_id, jdoc); } int msg_parser::extract_create_message(create_msg &msg) const { return json::extract_create_message(msg, jdoc); } + + int msg_parser::extract_initiate_message(initiate_msg &msg) const + { + return json::extract_initiate_message(msg, jdoc); + } int msg_parser::extract_destroy_message(destroy_msg &msg) const { @@ -33,14 +38,14 @@ namespace msg return json::extract_stop_message(msg, jdoc); } - void msg_parser::build_response(std::string &msg, std::string_view response_type, std::string_view reply_for, std::string_view content) const + void msg_parser::build_response(std::string &msg, std::string_view response_type, std::string_view reply_for, std::string_view content, const bool json_content) const { - json::build_response(msg, response_type, reply_for, content); + json::build_response(msg, response_type, reply_for, content, json_content); } - void msg_parser::build_create_response(std::string &msg, const hp::instance_info &info, std::string_view reply_for) const + void msg_parser::build_create_response(std::string &msg, const hp::instance_info &info) const { - json::build_create_response(msg, info, reply_for); + json::build_create_response(msg, info); } } // namespace msg \ No newline at end of file diff --git a/src/msg/msg_parser.hpp b/src/msg/msg_parser.hpp index 2617f0e..44e556c 100644 --- a/src/msg/msg_parser.hpp +++ b/src/msg/msg_parser.hpp @@ -13,13 +13,14 @@ namespace msg public: int parse(std::string_view message); - int extract_type(std::string &extracted_type) const; + int extract_type_and_id(std::string &extracted_type, std::string &extracted_id) const; int extract_create_message(create_msg &msg) const; + int extract_initiate_message(initiate_msg &msg) const; int extract_destroy_message(destroy_msg &msg) const; int extract_start_message(start_msg &msg) const; int extract_stop_message(stop_msg &msg) const; - void build_response(std::string &msg, std::string_view response_type, std::string_view reply_for, std::string_view content) const; - void build_create_response(std::string &msg, const hp::instance_info &info, std::string_view reply_for) const; + void build_response(std::string &msg, std::string_view response_type, std::string_view reply_for, std::string_view content, const bool json_content = false) const; + void build_create_response(std::string &msg, const hp::instance_info &info) const; }; } // namespace msg diff --git a/src/pchheader.hpp b/src/pchheader.hpp index 1134ffd..65b1e5b 100644 --- a/src/pchheader.hpp +++ b/src/pchheader.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/src/util/util.cpp b/src/util/util.cpp index 8fe5169..9a629e6 100644 --- a/src/util/util.cpp +++ b/src/util/util.cpp @@ -257,6 +257,26 @@ namespace util return 0; } + /** + * Converts given string to a uint16_t. A wrapper function for std::stoul. + * @param str String variable. + * @param result Variable to store the answer from the conversion. + * @return Returns 0 in a successful conversion and -1 on error. + */ + int stoul(const std::string &str, uint16_t &result) + { + try + { + result = std::stoul(str); + } + catch (const std::exception &e) + { + // Return -1 if any exceptions are captured. + return -1; + } + return 0; + } + /** * Construct the user contract directory path when username is given. * @param username Username of the user. @@ -306,4 +326,67 @@ namespace util } } + /** + * Writes the given json doc to a file. + * @param fd File descriptor to the open file. + * @param d A valid JSON document. + * @return 0 on success. -1 on failure. + */ + int write_json_file(const int fd, const jsoncons::ojson &d) + { + std::string json; + // Convert json object to a string. + try + { + jsoncons::json_options options; + options.object_array_line_splits(jsoncons::line_split_kind::multi_line); + options.spaces_around_comma(jsoncons::spaces_option::no_spaces); + std::ostringstream os; + os << jsoncons::pretty_print(d, options); + json = os.str(); + os.clear(); + } + catch (const std::exception &e) + { + LOG_ERROR << "Converting modified hp config json to string failed. "; + return -1; + } + + if (ftruncate(fd, 0) == -1 || write(fd, json.data(), json.size()) == -1) + { + LOG_ERROR << "Writing modified hp config file failed. "; + return -1; + } + return 0; + } + + /** + * Reads the given file to a json doc. + * @param fd File descriptor to the open file. + * @param d JSON document to be populated. + * @return 0 on success. -1 on failure. + */ + int read_json_file(const int fd, jsoncons::ojson &d) + { + std::string buf; + if (util::read_from_fd(fd, buf) == -1) + { + std::cerr << "Error reading from the config file. " << errno << '\n'; + return -1; + } + + try + { + d = jsoncons::ojson::parse(buf, jsoncons::strict_json_parsing()); + } + catch (const std::exception &e) + { + std::cerr << "Invalid config file format. " << e.what() << '\n'; + return -1; + } + buf.clear(); + + return 0; + } + } // namespace util diff --git a/src/util/util.hpp b/src/util/util.hpp index 6789c5c..40da1c5 100644 --- a/src/util/util.hpp +++ b/src/util/util.hpp @@ -46,12 +46,18 @@ namespace util int stoi(const std::string &str, int &result); + int stoul(const std::string &str, uint16_t &result); + const std::string get_user_contract_dir(const std::string &username, std::string_view container_name); int get_system_user_info(std::string_view username, user_info &user_info); void find_and_replace(std::string &str, std::string_view find, std::string_view replace); + int write_json_file(const int fd, const jsoncons::ojson &d); + + int read_json_file(const int fd, jsoncons::ojson &d); + } // namespace util #endif