Two step instance creation and refactoring (#20)

This commit is contained in:
Chalith Desaman
2021-06-29 16:09:04 +05:30
committed by GitHub
parent c8df2e75ac
commit 43cb7e8ca1
16 changed files with 712 additions and 286 deletions

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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 <validator> or <observer>');
if (role && role != 'validator' && role != 'observer') {
console.error('Invalid role. (Should be "validator" or "observer").')
break;
}
history = await askForInput('History <mode{full|custom},max_primary_shards{number},max_raw_shards{number}>');
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 <host1:port1>,<host2:port2>,...');
unl = await askForInput('Comma seperated UNL <pubkey1>,<pubkey2>,...');
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;
}

View File

@@ -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;
}

View File

@@ -202,6 +202,14 @@ namespace conf
try
{
const jsoncons::ojson &hp = d["hp"];
cfg.hp.host_address = hp["host_address"].as<std::string>();
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<uint16_t>();
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);

View File

@@ -56,6 +56,7 @@ namespace conf
struct hp_config
{
std::string host_address;
uint16_t init_peer_port;
uint16_t init_user_port;
};

View File

@@ -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 <container_name> 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<std::string>();
hpfs_log_level = d["hpfs"]["log"]["log_level"].as<std::string>();
}
catch (const std::exception &e)
{
@@ -619,7 +627,7 @@ namespace hp
}
const std::unordered_set<std::string> 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<std::string>() == "custom" && d["node"]["history_config"]["max_primary_shards"].as<uint64_t>() == 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.

View File

@@ -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<std::string> &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

View File

@@ -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<std::string>())
{
LOG_ERROR << "Invalid type value.";
return -1;
}
extracted_type = d[msg::FLD_TYPE].as<std::string>();
if (!d.contains(msg::FLD_ID))
{
LOG_ERROR << "Field id is missing.";
return -1;
}
if (!d[msg::FLD_ID].is<std::string>())
{
LOG_ERROR << "Invalid id value.";
return -1;
}
extracted_id = d[msg::FLD_ID].as<std::string>();
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<std::string>())
{
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<std::string>();
pubkey = d[msg::FLD_PUBKEY].as<std::string>();
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": "<pubkey of the owner>",
* "container_name": "<container name>",
* "peers": [<'ip:port' peer list>],
* "unl": [<hex unl pubkey list>],
* "role": <role>,
* "history": <history mode>,
* "max_primary_shards": <number of max primary shards>,
* "max_raw_shards": <number of 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<std::string>())
{
LOG_ERROR << "Invalid container_name value.";
return -1;
}
msg.container_name = d[msg::FLD_CONTAINER_NAME].as<std::string>();
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<std::string> splitted;
for (auto &val : d[msg::FLD_PEERS].array_range())
{
if (!val.is<std::string>())
{
LOG_ERROR << "Invalid peer value.";
return -1;
}
const std::string peer = val.as<std::string>();
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<std::string>())
{
LOG_ERROR << "Invalid unl pubkey value.";
return -1;
}
const std::string unl_pubkey = val.as<std::string>();
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<std::string>())
{
LOG_ERROR << "Invalid role value.";
return -1;
}
msg.role = d[msg::FLD_ROLE].as<std::string>();
}
if (d.contains(msg::FLD_HISTORY))
{
if (!d[msg::FLD_HISTORY].is<std::string>())
{
LOG_ERROR << "Invalid history value.";
return -1;
}
msg.history = d[msg::FLD_HISTORY].as<std::string>();
}
if (d.contains(msg::FLD_MAX_P_SHARDS))
{
if (!d[msg::FLD_MAX_P_SHARDS].empty() && !d[msg::FLD_MAX_P_SHARDS].is<uint64_t>())
{
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<uint64_t>();
}
if (d.contains(msg::FLD_MAX_R_SHARDS))
{
if (!d[msg::FLD_MAX_R_SHARDS].empty() && !d[msg::FLD_MAX_R_SHARDS].is<uint64_t>())
{
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<uint64_t>();
}
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': '<reply_for>'
* 'type': '<message type>',
* "name": "<container name>"
* "username": "<instance user name>""
* "ip": "<ip of the container>"
@@ -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;

View File

@@ -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

View File

@@ -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<conf::host_ip_port> peers;
std::set<std::string> unl;
std::string role;
std::string history;
std::optional<uint64_t> max_primary_shards;
std::optional<uint64_t> 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";

View File

@@ -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

View File

@@ -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

View File

@@ -11,6 +11,7 @@
#include <iostream>
#include <jsoncons/json.hpp>
#include <libgen.h>
#include <set>
#include <string>
#include <string_view>
#include <sqlite3.h>

View File

@@ -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

View File

@@ -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