diff --git a/CMakeLists.txt b/CMakeLists.txt index 33c61535..7d935458 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -178,6 +178,15 @@ add_library(graft STATIC ${PROJECT_SOURCE_DIR}/modules/mongoose/mongoose.c ${PROJECT_SOURCE_DIR}/src/backtrace.cpp ${PROJECT_SOURCE_DIR}/src/server.cpp + ${PROJECT_SOURCE_DIR}/src/supernode/server/server.cpp + ${PROJECT_SOURCE_DIR}/src/supernode/server/config.cpp + ${PROJECT_SOURCE_DIR}/src/supernode/server/config_loader.cpp + ${PROJECT_SOURCE_DIR}/src/supernode/node.cpp + ${PROJECT_SOURCE_DIR}/src/supernode/config.cpp + ${PROJECT_SOURCE_DIR}/src/supernode/config_loader.cpp + ${PROJECT_SOURCE_DIR}/src/supernode/route/router.cpp + ${PROJECT_SOURCE_DIR}/src/supernode/route/route_set.cpp + ${PROJECT_SOURCE_DIR}/src/supernode/route/handler3.cpp ${PROJECT_SOURCE_DIR}/src/log.cpp ${PROJECT_SOURCE_DIR}/src/supernode.cpp ${PROJECT_SOURCE_DIR}/src/sys_info.cpp diff --git a/include/common/utils.h b/include/common/utils.h index 6768c7a9..06cbc6de 100644 --- a/include/common/utils.h +++ b/include/common/utils.h @@ -1,14 +1,19 @@ -#ifndef UTILS_H -#define UTILS_H + +#pragma once #include #include +#include + +namespace graft { class ConnectionManager; } + +namespace graft::supernode::utils { -namespace graft { -namespace utils { +std::string base64_decode(const std::string& encoded_data); +std::string base64_encode(const std::string& data); -std::string base64_decode(const std::string &encoded_data); -std::string base64_encode(const std::string &data); +std::string get_home_dir(void); // TODO: make it crossplatform +std::string trim_comments(std::string s); template T random_number(T startRange, T endRange) @@ -19,7 +24,9 @@ T random_number(T startRange, T endRange) return dist(mt); } -} +void check_routes(graft::ConnectionManager& cm); +void split_string_by_separator(const std::string& src, char sep, std::set& dst); +void remove_duplicates_from_vector(std::vector& vec); + } -#endif // UTILS_H diff --git a/include/connection.h b/include/connection.h index 8770cca3..1751f874 100644 --- a/include/connection.h +++ b/include/connection.h @@ -67,6 +67,7 @@ class Looper final : public TaskManager { public: Looper(const ConfigOpts& copts); + Looper(const Config& cfg); virtual ~Looper(); void serve(); diff --git a/include/context.h b/include/context.h index 29e30f63..5ff9e937 100644 --- a/include/context.h +++ b/include/context.h @@ -19,6 +19,7 @@ namespace graft { class ConfigOpts; } namespace graft::supernode::system_info { class Counter; } +namespace graft::supernode::server { class Config; } namespace graft { @@ -50,10 +51,13 @@ class GlobalContextMapFriend : protected GlobalContextMap }; using SysInfoCounter = supernode::system_info::Counter; +using supernode::server::Config; class Context { public: + const Config& config(void) const; + void config(const Config& cfg); const ConfigOpts& config_opts(void) const; void config_opts(const ConfigOpts& copts); diff --git a/include/inout.h b/include/inout.h index 5ecb5c69..8f72eb83 100644 --- a/include/inout.h +++ b/include/inout.h @@ -75,8 +75,10 @@ struct mg_str; //from mongoose.h * ); */ -namespace graft +namespace graft { + using namespace supernode; + namespace serializer { class JsonParseError : public std::runtime_error diff --git a/include/requestdefines.h b/include/requestdefines.h index 5db4fc8f..20edd3cc 100644 --- a/include/requestdefines.h +++ b/include/requestdefines.h @@ -73,6 +73,7 @@ static const std::string CONTEXT_SALE_DETAILS_RESULT(":sale_details_result"); static const double AUTHSAMPLE_FEE_PERCENTAGE = 0.5; static const std::string CONTEXT_KEY_CONFIG_OPTS(":config-opts"); +static const std::string CONTEXT_KEY_CONFIG(":config"); static const std::string CONTEXT_KEY_RUNTIME_SYS_INFO(":runtime-sys-info"); namespace graft { diff --git a/include/rta/fullsupernodelist.h b/include/rta/fullsupernodelist.h index 8df6b783..412c9043 100644 --- a/include/rta/fullsupernodelist.h +++ b/include/rta/fullsupernodelist.h @@ -13,14 +13,10 @@ #include #include +namespace graft::utils { class ThreadPool; } namespace graft { -namespace utils { - class ThreadPool; -} - - class FullSupernodeList { public: @@ -45,7 +41,7 @@ class FullSupernodeList * \return - number of loaded supernode wallets */ size_t loadFromDir(const std::string &base_dir); - + /*! * \brief loadFromDirThreaded - loads list from directory. * \param base_dir - directory where to search for wallets @@ -53,7 +49,7 @@ class FullSupernodeList * \return - number of loaded supernodes */ size_t loadFromDirThreaded(const std::string &base_dir, size_t &found_wallets); - + /*! * \brief remove - removes Supernode from list. closes it's wallet and frees memory * \param address - supernode address @@ -140,7 +136,7 @@ class FullSupernodeList bool m_testnet; DaemonRpcClient m_rpc_client; mutable boost::shared_mutex m_access; - std::unique_ptr m_tp; + std::unique_ptr m_tp; std::atomic_size_t m_refresh_counter; }; diff --git a/include/server.h b/include/server.h index 3e08c6a0..c4b09fc4 100644 --- a/include/server.h +++ b/include/server.h @@ -43,7 +43,7 @@ class GraftServer //the order of members is important because of destruction order. std::unique_ptr m_looper; private: - void initLog(int log_level); + //void initLog(int log_level); void initGlobalContext(); void initConnectionManagers(); void serve(); diff --git a/include/supernode/config.h b/include/supernode/config.h new file mode 100644 index 00000000..97d210c9 --- /dev/null +++ b/include/supernode/config.h @@ -0,0 +1,22 @@ + +#pragma once + +#include "supernode/server/config.h" + +namespace graft::supernode { + +struct Config : public server::Config +{ + std::string data_dir; // base directory where supernode stake wallet and other supernodes wallets are located + std::string stake_wallet_name; + size_t stake_wallet_refresh_interval_ms; + + std::string watchonly_wallets_path; // path to watch-only wallets (supernodes) + bool testnet; // testnet flag + + + Config(void); +}; + +} + diff --git a/include/supernode/config_loader.h b/include/supernode/config_loader.h new file mode 100644 index 00000000..6f0da7fa --- /dev/null +++ b/include/supernode/config_loader.h @@ -0,0 +1,21 @@ + +#pragma once + +namespace graft::supernode { + +class Config; + +class ConfigLoader +{ +public: + ConfigLoader(void); + ~ConfigLoader(void); + + ConfigLoader(const ConfigLoader&) = delete; + ConfigLoader& operator = (const ConfigLoader&) = delete; + + bool load(int argc, const char** argv, Config& cfg); +}; + +} + diff --git a/include/supernode/node.h b/include/supernode/node.h new file mode 100644 index 00000000..815c3a69 --- /dev/null +++ b/include/supernode/node.h @@ -0,0 +1,34 @@ +#pragma once + +#include "supernode/server/server.h" +#include "supernode/config.h" + +namespace graft::supernode { + +using server::Server; + +class Node : public Server +{ +public: + Node(void); + ~Node(void); + + bool run(const Config& cfg); + +protected: + virtual void initMisc(server::Config& cfg) override; + //virtual bool initConfigOption(int argc, const char** argv, ConfigOpts& configOpts) override; + virtual void initRouters() override; + +private: + void prepareDataDirAndSupernodes(); + void startSupernodePeriodicTasks(); + void setHttpRouters(ConnectionManager& httpcm); + void setCoapRouters(ConnectionManager& coapcm); + +private: + Config m_cfg; +}; + +} + diff --git a/include/supernode/route/data.h b/include/supernode/route/data.h new file mode 100644 index 00000000..26f5640b --- /dev/null +++ b/include/supernode/route/data.h @@ -0,0 +1,34 @@ + +#pragma once + +#include +#include +#include + +namespace graft { +enum class Status: int; +class Context; +class InHttp; +class OutHttp; +} + +namespace graft::supernode::route { + +using Vars = std::multimap; +using graft::Context; +using graft::Status; + +template +using HandlerT = std::function; + +using Input = graft::InHttp; +using Output = graft::OutHttp; + +using Handler = HandlerT; + +} + + + + + diff --git a/include/supernode/route/handler3.h b/include/supernode/route/handler3.h new file mode 100644 index 00000000..2675cb13 --- /dev/null +++ b/include/supernode/route/handler3.h @@ -0,0 +1,34 @@ + +#pragma once + +#include "supernode/route/data.h" + +#include + +namespace graft::supernode::route { + +struct Handler3 +{ +public: + Handler pre_action; + Handler worker_action; + Handler post_action; + std::string name; + + Handler3() = default; + ~Handler3() = default; + + Handler3(const Handler3&) = default; + Handler3(Handler3&&) = default; + + Handler3& operator = (const Handler3&) = default; + Handler3& operator = (Handler3&&) = default; + + Handler3(const Handler& pre_action, const Handler& action, const Handler& post_action, const std::string& name = std::string()); + Handler3(Handler&& pre_action, Handler&& action, Handler&& post_action, std::string&& name = std::string()); + Handler3(const Handler& worker_action); + Handler3(Handler&& worker_action); +}; + +} + diff --git a/include/supernode/route/job_params.h b/include/supernode/route/job_params.h new file mode 100644 index 00000000..686ccc90 --- /dev/null +++ b/include/supernode/route/job_params.h @@ -0,0 +1,22 @@ + +#pragma once + +#include "supernode/route/data.h" +#include "supernode/route/handler3.h" +#include "inout.h" + +namespace graft::supernode::route { + +struct JobParams +{ + Input input; + Vars vars; + Handler3 h3; +}; + +} + + + + + diff --git a/include/supernode/route/route_set.h b/include/supernode/route/route_set.h new file mode 100644 index 00000000..b11b59e2 --- /dev/null +++ b/include/supernode/route/route_set.h @@ -0,0 +1,42 @@ + +#pragma once + +#include "supernode/route/handler3.h" + +#include +#include + +namespace graft::supernode::route { + +struct Route +{ + std::string endpoint; + int method; + Handler3 h3; + + Route(const std::string& end_poirnt, int method, const Handler3& h3); +}; + +std::string method_to_str(int method); + +class RouteSet +{ +public: + explicit RouteSet(const std::string& prefix = std::string()); + RouteSet(RouteSet&&); + ~RouteSet(); + + void add(const std::string& endpoint, int method, const Handler3& ph3); + void add(const std::string& endpoint, int method, const Handler3&& ph3); + + std::string dbg_dump(const std::string& prefix = std::string()) const; + + std::forward_list routes(void); + const std::forward_list routes(void) const; + +private: + std::forward_list m_routes; + std::string m_endpointPrefix; +}; + +} diff --git a/include/supernode/route/router.h b/include/supernode/route/router.h new file mode 100644 index 00000000..7cd57ac4 --- /dev/null +++ b/include/supernode/route/router.h @@ -0,0 +1,36 @@ + +#pragma once + +#include +#include + +struct _node; +typedef struct _node R3Node; + +namespace graft::supernode::route { + +struct JobParams; +struct RouteSet; + +class Router +{ +public: + Router(void); + ~Router(void); + + bool arm(void); + bool match(const std::string& target, int method, JobParams& params) const; + void add_route_set(RouteSet& rs); + + std::string dbg_dump_routes(void) const; + void dbg_dump_R3Tree(int level = 0) const; + std::string dbg_check_conflict_routes(void) const; + +private: + bool m_compiled; + R3Node* m_node; + std::forward_list m_routes; +}; + +} + diff --git a/include/supernode/server/config.h b/include/supernode/server/config.h new file mode 100644 index 00000000..3f317521 --- /dev/null +++ b/include/supernode/server/config.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +namespace graft::supernode::server { + +struct Config +{ + std::string config_filename; + std::string http_address; + std::string coap_address; + double http_connection_timeout; + double upstream_request_timeout; + int workers_count; + int worker_queue_len; + std::string cryptonode_rpc_address; + int timer_poll_interval_ms; + int log_trunc_to_size; + std::vector graftlet_dirs; + int lru_timeout_ms; + + std::string self_dir; + std::vector uri_subst; + + std::string log_level; + std::string log_filename; + std::string log_format; + bool log_console; + + + Config(void); +}; + +} diff --git a/include/supernode/server/config_loader.h b/include/supernode/server/config_loader.h new file mode 100644 index 00000000..24127632 --- /dev/null +++ b/include/supernode/server/config_loader.h @@ -0,0 +1,21 @@ + +#pragma once + +namespace graft::supernode::server { + +class Config; + +class ConfigLoader +{ +public: + ConfigLoader(void); + ~ConfigLoader(void); + + ConfigLoader(const ConfigLoader&) = delete; + ConfigLoader& operator = (const ConfigLoader&) = delete; + + bool load(int argc, const char** argv, Config& cfg); +}; + +} + diff --git a/include/supernode/server/server.h b/include/supernode/server/server.h new file mode 100644 index 00000000..c9181f1e --- /dev/null +++ b/include/supernode/server/server.h @@ -0,0 +1,69 @@ + +#pragma once + +#include "connection.h" + +namespace graftlet { class GraftletLoader; } +namespace graft::supernode::system_info { class Counter; } + +namespace graft::supernode::server { + +class Config; +using SysInfoCounter = graft::supernode::system_info::Counter; + +enum RunResult : int +{ + SignalShutdown, + SignalTerminate, + SignalRestart, + UnexpectedOk, +}; + +class Server +{ +public: + Server(void); + virtual ~Server(void); + + Server(const Server&) = delete; + Server& operator = (const Server&) = delete; + + bool init(Config& cfg); + RunResult run(void); + +protected: + virtual void initMisc(Config& cfg) {} + virtual void initRouters(void) {} + + bool ready(void) const; + void stop(bool force = false); + ConnectionManager* getConMgr(const ConnectionManager::Proto& proto); + + //the order of members is important because of destruction order. + std::unique_ptr m_looper; + +private: + void serve(void); + + void initGlobalContext(void); + void initConnectionManagers(void); + + static void initSignals(void); + void initGraftlets(void); + void initGraftletRouters(void); + + void create_looper(Config& cfg); + void create_system_info_counter(void); + + void add_global_ctx_cleaner(void); + + Config& config(void); + +private: + std::unique_ptr m_graftletLoader; + std::map> m_conManagers; + std::unique_ptr m_sys_info; +}; + +} + diff --git a/include/task.h b/include/task.h index 26623794..0927c327 100644 --- a/include/task.h +++ b/include/task.h @@ -10,6 +10,9 @@ #include "serveropts.h" #include #include "handler_api.h" + +#include "supernode/server/config.h" + #include #include @@ -33,6 +36,7 @@ struct mg_mgr; struct mg_connection; + namespace graft { extern std::string client_addr(mg_connection* client); @@ -47,6 +51,7 @@ using BaseTaskPtr = std::shared_ptr; class GJPtr; using TPResQueue = tp::MPMCBoundedQueue< GJPtr >; using GJ = GraftJob; +using Config = graft::supernode::server::Config; ////////////// /// \brief The GJPtr class @@ -188,6 +193,7 @@ class TaskManager : private HandlerAPI { public: TaskManager(const ConfigOpts& copts); + TaskManager(const Config& cfg); virtual ~TaskManager(); void sendUpstream(BaseTaskPtr bt); @@ -198,6 +204,10 @@ class TaskManager : private HandlerAPI virtual mg_mgr* getMgMgr() = 0; GlobalContextMap& getGcm() { return m_gcm; } ConfigOpts& getCopts() { return m_copts; } + + GlobalContextMap& gcm(void) { return m_gcm; } + Config& config(void) { return m_cfg; } + TimerList& getTimerList() { return m_timerList; } static TaskManager* from(mg_mgr* mgr); @@ -228,6 +238,8 @@ class TaskManager : private HandlerAPI void checkPeriodicTaskIO(); ConfigOpts m_copts; + Config m_cfg; + private: void Execute(BaseTaskPtr bt); void processForward(BaseTaskPtr bt); diff --git a/src/common/utils.cpp b/src/common/utils.cpp index 3bcd29d0..8f588d37 100644 --- a/src/common/utils.cpp +++ b/src/common/utils.cpp @@ -1,20 +1,76 @@ -#include "utils/utils.h" -#include // epee::string_coding uses std::locale but misses include -#include +#include "common/utils.h" +#include "connection.h" +#include "string_coding.h" + +#include // epee::string_coding uses std::locale but misses include +#include -namespace graft { -namespace utils { +namespace graft::supernode::utils { -std::string base64_decode(const std::string &encoded_data) +std::string base64_decode(const std::string& encoded_data) { return epee::string_encoding::base64_decode(encoded_data); } -std::string base64_encode(const std::string &data) +std::string base64_encode(const std::string& data) { return epee::string_encoding::base64_encode(data); } +std::string get_home_dir(void) +{ + return std::string(getenv("HOME")); } + +std::string trim_comments(std::string s) //remove ;; tail +{ + const std::size_t pos = s.find(";;"); + + if(pos != std::string::npos) + s = s.substr(0, pos); + + boost::trim_right(s); + return s; } + +void check_routes(graft::ConnectionManager& cm) +{//check conflicts in routes + std::string s = cm.dbgCheckConflictRoutes(); + if(!s.empty()) + { + std::cout << std::endl << "==> " << cm.getProto() << " manager.dbgDumpRouters()" << std::endl; + std::cout << cm.dbgDumpRouters(); + + //if you really need dump of r3tree uncomment two following lines + //std::cout << std::endl << std::endl << "==> manager.dbgDumpR3Tree()" << std::endl; + //manager.dbgDumpR3Tree(); + + throw std::runtime_error("Routes conflict found:" + s); + } +} + +void split_string_by_separator(const std::string& src, const char sep, std::set& dst) +{ + for(std::string::size_type s = 0;;) + { + std::string::size_type e = src.find(sep, s); + if(e == std::string::npos) + { + dst.insert(src.substr(s)); + break; + } + dst.insert(src.substr(s, e - s)); + s = e + 1; + } +} + +void remove_duplicates_from_vector(std::vector& vec) +{ + std::set set; + auto end = std::remove_if(vec.begin(), vec.end(), [&set](const auto& s)->bool { return !set.emplace(s).second; }); + vec.erase(end, vec.end()); +} + +} + diff --git a/src/connection.cpp b/src/connection.cpp index ddc6bab2..92fc14b2 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -113,14 +113,20 @@ void UpstreamSender::ev_handler(mg_connection *upstream, int ev, void *ev_data) } Looper::Looper(const ConfigOpts& copts) - : TaskManager(copts) - , m_mgr(std::make_unique()) +: TaskManager(copts) +, m_mgr(std::make_unique()) { mg_mgr_init(m_mgr.get(), this, cb_event); } +Looper::Looper(const Config& cfg) +: TaskManager(cfg) +, m_mgr(std::make_unique()) +{ + mg_mgr_init(m_mgr.get(), this, cb_event); +} -Looper::~Looper() +Looper::~Looper(void) { mg_mgr_free(m_mgr.get()); } diff --git a/src/context.cpp b/src/context.cpp index 6b22e2a1..15444b38 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -7,26 +7,38 @@ namespace graft { const ConfigOpts& Context::config_opts(void) const { - const ConfigOpts* p = global.get(CONTEXT_KEY_CONFIG_OPTS, (const ConfigOpts*)nullptr); - assert(p); - return *p; + const ConfigOpts* p = global.get(CONTEXT_KEY_CONFIG_OPTS, (const ConfigOpts*)nullptr); + assert(p); + return *p; } void Context::config_opts(const ConfigOpts& copts) { - global[CONTEXT_KEY_CONFIG_OPTS] = &copts; + global[CONTEXT_KEY_CONFIG_OPTS] = &copts; +} + +const Config& Context::config(void) const +{ + const Config* p = global.get(CONTEXT_KEY_CONFIG, (const Config*)nullptr); + assert(p); + return *p; +} + +void Context::config(const Config& cfg) +{ + global[CONTEXT_KEY_CONFIG] = &cfg; } SysInfoCounter& Context::runtime_sys_info(void) { - SysInfoCounter* c = global.get(CONTEXT_KEY_RUNTIME_SYS_INFO, (SysInfoCounter*)nullptr); - assert(c); - return *c; + SysInfoCounter* c = global.get(CONTEXT_KEY_RUNTIME_SYS_INFO, (SysInfoCounter*)nullptr); + assert(c); + return *c; } void Context::runtime_sys_info(SysInfoCounter& sic) { - global[CONTEXT_KEY_RUNTIME_SYS_INFO] = &sic; + global[CONTEXT_KEY_RUNTIME_SYS_INFO] = &sic; } } diff --git a/src/main.cpp b/src/main.cpp index abb15321..28227205 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,24 @@ + #include "server.h" #include "supernode.h" #include "backtrace.h" #include "graft_exception.h" +#include "supernode/node.h" +#include "supernode/config.h" +#include "supernode/config_loader.h" + +namespace graft +{ +int main_1(int argc, const char** argv); +int main_2(int argc, const char** argv); +} + +int main(int argc, const char** argv) +{ + return graft::main_1(argc, argv); +} + namespace graft { @@ -38,10 +54,7 @@ void terminate() prev_terminate(); } -} //namespace graft - - -int main(int argc, const char** argv) +int main_1(int argc, const char** argv) { graft::prev_terminate = std::set_terminate( graft::terminate ); @@ -65,3 +78,44 @@ int main(int argc, const char** argv) return 0; } + + +int main_2(int argc, const char** argv) +{ + graft::prev_terminate = std::set_terminate(graft::terminate); + + using graft::supernode::Config; + using graft::supernode::ConfigLoader; + try + { + Config cfg; + if(ConfigLoader().load(argc, argv, cfg) && !Node().run(cfg)) + return -2; + } + catch(const graft::exit_error& e) + { + std::cerr << "The program is terminated because of error: " << e.what() << std::endl; + return -1; + } + catch(const std::exception& e) + { + std::cerr << "Exception thrown: " << e.what() << std::endl; + throw; + return -1; + } + catch(...) + { + std::cerr << "Exception of unknown type!\n"; + throw; + return -1; + } + + return 0; +} + +} + + + + + diff --git a/src/requests/saledetailsrequest.cpp b/src/requests/saledetailsrequest.cpp index e3a3efd6..4a9a5178 100644 --- a/src/requests/saledetailsrequest.cpp +++ b/src/requests/saledetailsrequest.cpp @@ -144,7 +144,7 @@ Status handleClientRequest(const Router::vars_t& vars, const graft::Input& input UnicastRequestJsonRpc unicastReq; unicastReq.params.sender_address = supernode->walletAddress(); size_t maxIndex = authSample.size() - 1; - size_t randomIndex = utils::random_number(0, maxIndex); + size_t randomIndex = supernode::utils::random_number(0, maxIndex); unicastReq.params.receiver_address = authSample.at(randomIndex)->walletAddress(); MDEBUG("requesting sale details from remote supernode: " << unicastReq.params.receiver_address diff --git a/src/server.cpp b/src/server.cpp index 9aeff6eb..609ad19a 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1,9 +1,11 @@ + #include "server.h" #include "backtrace.h" #include "GraftletLoader.h" +#include "sys_info.h" + #include #include -#include "sys_info.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "supernode.server" diff --git a/src/supernode/config.cpp b/src/supernode/config.cpp new file mode 100644 index 00000000..329013ee --- /dev/null +++ b/src/supernode/config.cpp @@ -0,0 +1,12 @@ + +#include "supernode/config.h" + +namespace graft::supernode { + +Config::Config(void) +: stake_wallet_refresh_interval_ms(0) +, testnet(false) +{} + +} + diff --git a/src/supernode/config_loader.cpp b/src/supernode/config_loader.cpp new file mode 100644 index 00000000..f771c656 --- /dev/null +++ b/src/supernode/config_loader.cpp @@ -0,0 +1,57 @@ + +#include "supernode/config_loader.h" +#include "supernode/config.h" +#include "supernode/server/config_loader.h" + +namespace graft::supernode { + +ConfigLoader::ConfigLoader(void) +{ +} + +ConfigLoader::~ConfigLoader(void) +{ +} + +bool ConfigLoader::load(const int argc, const char** argv, Config& cfg) +{ + if(!server::ConfigLoader().load(argc, argv, cfg)) + return false; + + //VarMap cmd_line_params; + //if(read_params_from_cmd_line(argc, argv, cfg, cmd_line_params)) + // return false; + + //PTree ini_params; + //read_params_from_ini_file(cfg, ini_params); + + //read_log_params(ini_params, cmd_line_params, cfg); + + return true; +} + + +/* +bool Node::initConfigOption(int argc, const char** argv, ConfigOpts& configOpts) +{ + bool res = Server::initConfigOption(argc, argv, configOpts); + if(!res) return res; + + ConfigOptsEx& coptsex = static_cast(configOpts); + assert(&m_configEx == &coptsex); + + boost::property_tree::ptree config; + boost::property_tree::ini_parser::read_ini(m_cfg.config_filename, config); + + const boost::property_tree::ptree& server_conf = config.get_child("server"); + m_cfg.data_dir = server_conf.get("data-dir"); + m_cfg.stake_wallet_name = server_conf.get("stake-wallet-name", "stake-wallet"); + m_cfg.stake_wallet_refresh_interval_ms = server_conf.get("stake-wallet-refresh-interval-ms", + consts::DEFAULT_STAKE_WALLET_REFRESH_INTERFAL_MS); + m_cfg.testnet = server_conf.get("testnet", false); + return res; +} +*/ + +} + diff --git a/src/supernode/node.cpp b/src/supernode/node.cpp new file mode 100644 index 00000000..b9ea8092 --- /dev/null +++ b/src/supernode/node.cpp @@ -0,0 +1,197 @@ + +#include "supernode/node.h" +#include "supernode/server/server.h" +#include "common/utils.h" + +#include "requests.h" +#include "sys_info.h" +#include "requestdefines.h" +#include "requests/sendsupernodeannouncerequest.h" +#include "rta/supernode.h" +#include "rta/fullsupernodelist.h" + +#include +#include + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "supernode.supernode" + +namespace consts { + static const char* DATA_PATH = "supernode/data"; + static const char* STAKE_WALLET_PATH = "stake-wallet"; + static const char* WATCHONLY_WALLET_PATH = "stake-wallet"; + static const size_t DEFAULT_STAKE_WALLET_REFRESH_INTERFAL_MS = 5 * 1000; +} + +namespace fs = boost::filesystem; + +namespace graft::supernode { + +using Path = fs::path; + +Node::Node(void) +{ +} + +Node::~Node(void) +{ +} + +bool Node::run(const Config& cfg) +{ + m_cfg = cfg; + if(!init(m_cfg)) + return false; + + while(server::RunResult::SignalRestart == Server::run()); + return true; +} + +void Node::initMisc(server::Config& srv_cfg) +{ + assert(&m_cfg == &(static_cast(srv_cfg))); + + prepareDataDirAndSupernodes(); + startSupernodePeriodicTasks(); +} + +void Node::prepareDataDirAndSupernodes() +{ + if(m_cfg.data_dir.empty()) + { + Path p = boost::filesystem::absolute(utils::get_home_dir()); + p /= ".graft/"; + p /= consts::DATA_PATH; + m_cfg.data_dir = p.string(); + } + + // create data directory if not exists + Path data_path(m_cfg.data_dir); + Path stake_wallet_path = data_path / "stake-wallet"; + Path watchonly_wallets_path = data_path / "watch-only-wallets"; + + if(!fs::exists(data_path)) + { + boost::system::error_code ec; + if(!fs::create_directories(data_path, ec)) + throw std::runtime_error(ec.message()); + + if(!fs::create_directories(stake_wallet_path, ec)) + throw std::runtime_error(ec.message()); + + if(!fs::create_directories(watchonly_wallets_path, ec)) + throw std::runtime_error(ec.message()); + } + + m_cfg.watchonly_wallets_path = watchonly_wallets_path.string(); + + MINFO("data path: " << data_path.string()); + MINFO("stake wallet path: " << stake_wallet_path.string()); + + // create supernode instance and put it into global context + graft::SupernodePtr supernode = boost::make_shared( + (stake_wallet_path / m_cfg.stake_wallet_name).string(), + "", // TODO + m_cfg.cryptonode_rpc_address, + m_cfg.testnet + ); + + supernode->setNetworkAddress(m_cfg.http_address + "/dapi/v2.0"); + + // create fullsupernode list instance and put it into global context + graft::FullSupernodeListPtr fsl = boost::make_shared( + m_cfg.cryptonode_rpc_address, m_cfg.testnet); + size_t found_wallets = 0; + MINFO("loading supernodes wallets from: " << watchonly_wallets_path.string()); + size_t loaded_wallets = fsl->loadFromDirThreaded(watchonly_wallets_path.string(), found_wallets); + + if (found_wallets != loaded_wallets) { + LOG_ERROR("found wallets: " << found_wallets << ", loaded wallets: " << loaded_wallets); + } + LOG_PRINT_L0("supernode list loaded"); + + // add our supernode as well, it wont be added from announce; + fsl->add(supernode); + + //put fsl into global context + assert(m_looper); + Context ctx(m_looper->gcm()); + ctx.global["supernode"] = supernode; + ctx.global[CONTEXT_KEY_FULLSUPERNODELIST] = fsl; + ctx.global["testnet"] = m_cfg.testnet; + ctx.global["watchonly_wallets_path"] = m_cfg.watchonly_wallets_path; + ctx.global["cryptonode_rpc_address"] = m_cfg.cryptonode_rpc_address; +} + +void Node::startSupernodePeriodicTasks() +{ + // update supernode every interval_ms + + if (m_cfg.stake_wallet_refresh_interval_ms > 0) { + size_t initial_interval_ms = 1000; + assert(m_looper); + m_looper->addPeriodicTask( + graft::Router::Handler3(nullptr, sendAnnounce, nullptr), + std::chrono::milliseconds(m_cfg.stake_wallet_refresh_interval_ms), + std::chrono::milliseconds(initial_interval_ms) + ); + } +} + +void Node::initRouters() +{ + ConnectionManager* httpcm = getConMgr("HTTP"); + setHttpRouters(*httpcm); + ConnectionManager* coapcm = getConMgr("COAP"); + setCoapRouters(*coapcm); +} + +void Node::setHttpRouters(ConnectionManager& httpcm) +{ + Router dapi_router("/dapi/v2.0"); + auto http_test = [](const Router::vars_t&, const Input&, Context&, Output&)->Status + { + std::cout << "blah-blah" << std::endl; + return Status::Ok; + }; + // Router::Handler3 h3_test1(http_test, nullptr, nullptr); + + // dapi_router.addRoute("/test", METHOD_GET, h3_test1); + // httpcm.addRouter(dapi_router); + + // Router http_router; + graft::registerRTARequests(dapi_router); + httpcm.addRouter(dapi_router); + + Router forward_router; + graft::registerForwardRequests(forward_router); + httpcm.addRouter(forward_router); + + Router health_router; + graft::registerHealthcheckRequests(health_router); + httpcm.addRouter(health_router); + + Router debug_router; + graft::registerDebugRequests(debug_router); + httpcm.addRouter(debug_router); +} + +void Node::setCoapRouters(ConnectionManager& coapcm) +{ + Router coap_router("/coap"); + auto coap_test = [](const Router::vars_t&, const Input&, Context&, Output&)->Status + { + std::cout << "blah" << std::endl; + return Status::Ok; + }; + Router::Handler3 h3_test(coap_test, nullptr, nullptr); + + coap_router.addRoute("/test", METHOD_GET, h3_test); + coap_router.addRoute("/test1", METHOD_GET, h3_test); + coap_router.addRoute("/test2", METHOD_GET, h3_test); + + coapcm.addRouter(coap_router); +} + +} + diff --git a/src/supernode/route/handler3.cpp b/src/supernode/route/handler3.cpp new file mode 100644 index 00000000..d6e1929f --- /dev/null +++ b/src/supernode/route/handler3.cpp @@ -0,0 +1,29 @@ + +#include "supernode/route/handler3.h" + +namespace graft::supernode::route { + +Handler3::Handler3(const Handler& pre_action, const Handler& action, const Handler& post_action, const std::string& name) +: pre_action(pre_action) +, worker_action(action) +, post_action(post_action) +, name(name) +{} + +Handler3::Handler3(Handler&& pre_action, Handler&& action, Handler&& post_action, std::string&& name) +: pre_action(std::move(pre_action)) +, worker_action(std::move(action)) +, post_action(std::move(post_action)) +, name(std::move(name)) +{} + +Handler3::Handler3(const Handler& worker_action) +: worker_action(worker_action) +{} + +Handler3::Handler3(Handler&& worker_action) +: worker_action(std::move(worker_action)) +{} + +} + diff --git a/src/supernode/route/route_set.cpp b/src/supernode/route/route_set.cpp new file mode 100644 index 00000000..1838f522 --- /dev/null +++ b/src/supernode/route/route_set.cpp @@ -0,0 +1,96 @@ + +#include "supernode/route/route_set.h" + +#include +#include +#include + +namespace graft::supernode::route { + +std::string method_to_str(const int method) +{ + constexpr const char* methpow[] = {"", "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"}; + assert((method & 0xFE) == method); + + std::string sm; + for(unsigned int b = 1, idx = 0; idx < 8; b <<= 1, ++idx) + { + if(!(method & b)) + continue; + + if(!sm.empty()) + sm += '|'; + + sm += methpow[idx]; + } + return sm; +} + +Route::Route(const std::string& end_poirnt, int method, const Handler3& h3) +: endpoint(end_poirnt) +, method(method) +, h3(h3) +{ +} + +RouteSet::RouteSet(const std::string& prefix) +: m_endpointPrefix(prefix) +{ +} + +RouteSet::RouteSet(RouteSet&& rs) +: m_routes(std::move(rs.m_routes)) +, m_endpointPrefix(std::move(rs.m_endpointPrefix)) +{ +} + +RouteSet::~RouteSet(void) +{ +} + +std::forward_list RouteSet::routes(void) +{ + return m_routes; +} + +const std::forward_list RouteSet::routes(void) const +{ + return m_routes; +} + +void RouteSet::add(const std::string& endpoint, const int method, const Handler3& ph3) +{ + m_routes.emplace_front(m_endpointPrefix + endpoint, method, ph3); +} + +void RouteSet::add(const std::string& endpoint, const int method, const Handler3&& ph3) +{ + m_routes.emplace_front(m_endpointPrefix + endpoint, method, std::move(ph3)); +} + +std::string RouteSet::dbg_dump(const std::string& prefix) const +{ + std::ostringstream ss; + for(const Route& r : m_routes) + { + auto ptr_to_str = [](const auto& ptr)->std::string + { + if(!ptr) + return "nullptr"; + + std::ostringstream ss; + ss << &ptr; + return ss.str(); + }; + + const std::string sm = method_to_str(r.method); + ss << prefix << sm << " " << r.endpoint << " (" << + ptr_to_str(r.h3.pre_action) << "," << + ptr_to_str(r.h3.worker_action) << "," << + ptr_to_str(r.h3.post_action) << ")" << std::endl; + } + return ss.str(); +} + +} + diff --git a/src/supernode/route/router.cpp b/src/supernode/route/router.cpp new file mode 100644 index 00000000..26c7b86b --- /dev/null +++ b/src/supernode/route/router.cpp @@ -0,0 +1,126 @@ + +#include "supernode/route/router.h" + +#include "supernode/route/handler3.h" +#include "supernode/route/route_set.h" +#include "supernode/route/job_params.h" + +#include +#include +#include +#include + +#include "r3.h" +// +//#include +//#include +//#include "inout.h" +//#include "context.h" + +namespace graft::supernode::route { + +Router::Router(void) +: m_compiled(false) +, m_node(nullptr) +{ + m_node = r3_tree_create(10); +} + +Router::~Router(void) +{ + r3_tree_free(m_node); +} + +bool Router::arm(void) +{ + std::for_each(m_routes.begin(), m_routes.end(), [this](RouteSet& rs) + { + std::for_each(rs.routes().begin(), rs.routes().end(), [this](Route& r) + { r3_tree_insert_route(m_node, r.method, r.endpoint.c_str(), &r); }); + }); + + char* err_str = nullptr; + int err = r3_tree_compile(m_node, &err_str); + + if(err) + std::cout << "error: " << std::string(err_str) << std::endl; + + return m_compiled = (err == 0); +} + +void Router::add_route_set(RouteSet& rs) +{ + m_routes.push_front(std::move(rs)); +} + +bool Router::match(const std::string& target, const int method, JobParams& params) const +{ + bool ret = false; + + match_entry* entry = match_entry_create(target.c_str()); + entry->request_method = method; + + if(R3Route* m = r3_tree_match_route(m_node, entry)) + { + for(size_t i = 0, cnt = entry->vars.tokens.size; i < cnt; ++i) + { + const auto& slug = entry->vars.slugs.entries[i]; + const auto& token = entry->vars.tokens.entries[i]; + + params.vars.emplace(std::make_pair( + std::move(std::string(slug.base, slug.len)), + std::move(std::string(token.base, token.len)))); + } + + params.h3 = static_cast(m->data)->h3; + ret = true; + } + match_entry_free(entry); + return ret; +} + +std::string Router::dbg_dump_routes(void) const +{ + std::string res; + int idx = 0; + for(const RouteSet& rs : m_routes) + { + std::ostringstream ss; + ss << "route_set[" << idx++ << "]->" << std::endl; + res += ss.str(); + res += rs.dbg_dump("\t"); + } + return res; +} + +void Router::dbg_dump_R3Tree(const int level) const +{ + assert(m_compiled); + r3_tree_dump(m_node, level); +} + +std::string Router::dbg_check_conflict_routes(void) const +{ + std::map map; + for(const RouteSet& rs : m_routes) + { + for(const Route& r : rs.routes()) + { + auto it = map.find(r.endpoint); + if(it == map.end()) + { + map[r.endpoint] = r.method; + continue; + } + + if(it->second & r.method) + return r.endpoint; + + it->second &= r.method; + } + } + return std::string(); +} + +} + diff --git a/src/supernode/server/config.cpp b/src/supernode/server/config.cpp new file mode 100644 index 00000000..de3c8352 --- /dev/null +++ b/src/supernode/server/config.cpp @@ -0,0 +1,18 @@ + +#include "supernode/server/config.h" + +namespace graft::supernode::server { + +Config::Config(void) +: http_connection_timeout(0) +, upstream_request_timeout(0) +, workers_count(0) +, worker_queue_len(0) +, timer_poll_interval_ms(0) +, log_trunc_to_size(0) +, lru_timeout_ms(0) +, log_console(false) +{} + +} + diff --git a/src/supernode/server/config_loader.cpp b/src/supernode/server/config_loader.cpp new file mode 100644 index 00000000..0383580f --- /dev/null +++ b/src/supernode/server/config_loader.cpp @@ -0,0 +1,279 @@ + +#include "supernode/server/config_loader.h" +#include "supernode/server/config.h" +#include "common/utils.h" +#include + +#include +#include +#include +#include + +namespace graft::supernode::server { + +namespace bpo = boost::program_options; +namespace bpt = boost::property_tree; + +using VarMap = bpo::variables_map; +using OptDesc = bpo::options_description; +using PTree = bpt::ptree; + +namespace fs = boost::filesystem; +using Path = fs::path; + +void read_graftlet_dirs(Config& cfg, const std::string& dir_list); + +void usage(const OptDesc& desc) +{ + std::string sigmsg = "Supported signals:\n" + " INT - Shutdown server gracefully closing all pending tasks.\n" + " TEMP - Shutdown server even if there are pending tasks.\n" + " HUP - Restart server with updated configuration parameters.\n"; + + std::cout << desc << "\n" << sigmsg << "\n"; +} + +void read_log_params_from_ini(const PTree& ini_cfg, Config& cfg) +{ + const PTree& ini = ini_cfg.get_child("logging"); + + boost::optional level = ini.get_optional("loglevel"); + if(level) + cfg.log_level = utils::trim_comments(level.get()); + + boost::optional log_file = ini.get_optional("logfile"); + if(log_file) + cfg.log_filename = utils::trim_comments(log_file.get()); + + boost::optional log_to_console = ini.get_optional("console"); + if(log_to_console) + cfg.log_console = log_to_console.get(); + + boost::optional log_fmt = ini.get_optional("log-format"); + if(log_fmt) + cfg.log_format = utils::trim_comments(log_fmt.get()); +} + +void read_log_params_from_cmd_line(const VarMap& cl_cfg, Config& cfg) +{ + if(cl_cfg.count("log-level")) cfg.log_level = cl_cfg["log-level"].as(); + if(cl_cfg.count("log-file")) cfg.log_filename = cl_cfg["log-file"].as(); + if(cl_cfg.count("log-console")) cfg.log_console = cl_cfg["log-console"].as(); + if(cl_cfg.count("log-format")) cfg.log_format = cl_cfg["log-format"].as(); +} + +bool read_params_from_cmd_line(const int argc, const char** argv, Config& cfg, VarMap& vm) +{ + bool exit = false; + + OptDesc desc("Allowed options"); + desc.add_options() + ("help", "produce help message") + ("config-file", bpo::value(), "config filename (config.ini by default)") + ("log-level", bpo::value(), "log-level. (3 by default), e.g. --log-level=2,supernode.task:INFO,supernode.server:DEBUG") + ("log-console", bpo::value(), "log to console. 1 or true or 0 or false. (true by default)") +#ifdef ELPP_SYSLOG + ("log-file", bpo::value(), "log file; set it to syslog if you want use the syslog instead") +#else + ("log-file", bpo::value(), "log file") +#endif + ("log-format", bpo::value(), "e.g. %datetime{%Y-%M-%d %H:%m:%s.%g} %level %logger %rfile %msg"); + + bpo::store(bpo::parse_command_line(argc, argv, desc), vm); + bpo::notify(vm); + + if((exit = vm.count("help"))) + usage(desc); + + if(vm.count("config-file")) + cfg.config_filename = vm["config-file"].as(); + + return exit; +} + +void read_params_from_ini_file(Config& cfg, PTree& ini) +{ + if(cfg.config_filename.empty()) + { + const Path self_path(cfg.self_dir); + cfg.config_filename = (self_path / "config.ini").string(); + } + + bpt::ini_parser::read_ini(cfg.config_filename, ini); + // now we have only following parameters + // [server] + // address : + // workers-count + // worker-queue-len + // stake-wallet # stake wallet filename (no path) + // [cryptonode] + // rpc-address : + // p2p-address : #maybe + // [upstream] + // uri_name=uri_value #pairs for uri substitution + // + // data directory structure + // . + // ├── stake-wallet + // │ ├── stake-wallet + // │ ├── stake-wallet.address.txt + // │ └── stake-wallet.keys + // └── watch-only-wallets + // ├── supernode_tier1_1 + // ├── supernode_tier1_1.address.txt + // ├── supernode_tier1_1.keys + // ................................ + // ├── supernode_tier1_2 + // ├── supernode_tier1_2.address.txt + // └── supernode_tier1_2.keys + // [graftlets] + // dirs + + const PTree& server_conf = ini.get_child("server"); + + cfg.http_address = server_conf.get("http-address"); + cfg.coap_address = server_conf.get("coap-address"); + cfg.timer_poll_interval_ms = server_conf.get("timer-poll-interval-ms"); + cfg.http_connection_timeout = server_conf.get("http-connection-timeout"); + cfg.workers_count = server_conf.get("workers-count"); + cfg.worker_queue_len = server_conf.get("worker-queue-len"); + cfg.upstream_request_timeout = server_conf.get("upstream-request-timeout"); + cfg.lru_timeout_ms = server_conf.get("lru-timeout-ms"); + + //configOpts.graftlet_dirs + const PTree& graftlets_conf = ini.get_child("graftlets"); + boost::optional gl_dirs = graftlets_conf.get_optional("dirs"); + read_graftlet_dirs(cfg, gl_dirs ? gl_dirs.get() : ""); + + const PTree& cryptonode_conf = ini.get_child("cryptonode"); + cfg.cryptonode_rpc_address = cryptonode_conf.get("rpc-address"); + + const PTree& log_conf = ini.get_child("logging"); + boost::optional log_trunc_to_size = log_conf.get_optional("trunc-to-size"); + cfg.log_trunc_to_size = (log_trunc_to_size) ? log_trunc_to_size.get() : -1; + + const PTree& uri_subst = ini.get_child("upstream"); + std::for_each(uri_subst.begin(), uri_subst.end(), [&cfg, &uri_subst](const auto& it) + { + cfg.uri_subst.emplace_back(it.first); + cfg.uri_subst.emplace_back(uri_subst.get(it.first)); + }); +} + +void read_log_params(const PTree& ini_cfg, const VarMap& cl_cfg, Config& cfg) +{ + cfg.log_level = "3"; + cfg.log_console = true; + + read_log_params_from_ini(ini_cfg, cfg); + read_log_params_from_cmd_line(cl_cfg, cfg); + + if(cfg.log_format.empty()) + { // default log format (we need to explicitly apply it here, otherwise full path to a file will be logged with monero default format) + static const char* DEFAULT_LOG_FORMAT = "%datetime{%Y-%M-%d %H:%m:%s.%g}\t%thread\t%level\t%logger\t%rfile:%line\t%msg"; + cfg.log_format = DEFAULT_LOG_FORMAT; + } +} + +/* +void init_log(const PTree& ini_cfg, const VarMap& cl_cfg, Config& cfg) +{ + +//#ifdef ELPP_SYSLOG +// if(log_filename == "syslog") +// { +// INITIALIZE_SYSLOG("graft_server"); +// mlog_syslog = true; +// mlog_configure("", false, log_format.empty()? nullptr : log_format.c_str()); +// } +// else +//#endif +// { +// mlog_configure(log_filename, log_console, log_format.empty()? nullptr : log_format.c_str()); +// } +// +// mlog_set_log(log_level.c_str()); +} +*/ + +ConfigLoader::ConfigLoader(void) +{ +} + +ConfigLoader::~ConfigLoader(void) +{ +} + +bool ConfigLoader::load(const int argc, const char** argv, Config& cfg) +{ + Path self_dir(argv[0]); + self_dir = self_dir.remove_filename(); + cfg.self_dir = self_dir.string(); + + VarMap cmd_line_params; + if(read_params_from_cmd_line(argc, argv, cfg, cmd_line_params)) + return false; + + PTree ini_params; + read_params_from_ini_file(cfg, ini_params); + + read_log_params(ini_params, cmd_line_params, cfg); + + return true; +} + +void read_graftlet_dirs(Config& cfg, const std::string& dir_list) +{ + cfg.graftlet_dirs.clear(); + + const Path self_dir(cfg.self_dir); + if(dir_list.empty()) + { + cfg.graftlet_dirs.push_back(fs::complete("graftlets", self_dir).string()); + return; + } + + std::set set; + utils::split_string_by_separator(dir_list, ':', set); + + const Path cur_dir(fs::current_path()); + + for(const auto& it : set) + { + const Path p(it); + if(p.is_relative()) + { + bool found = false; + Path path1 = fs::complete(p, self_dir); + if(fs::is_directory(path1)) + { + cfg.graftlet_dirs.push_back(path1.string()); + found = true; + } + + if(self_dir != cur_dir) + { + Path path2 = fs::complete(p, cur_dir); + if(fs::is_directory(path2)) + { + cfg.graftlet_dirs.push_back(path2.string()); + found = true; + } + } + + if(!found) + LOG_PRINT_L1("Graftlet path '" << p.string() << "' is not a directory"); + } + else + { + if(fs::is_directory(it)) + cfg.graftlet_dirs.emplace_back(p.string()); + else + LOG_PRINT_L1("Graftlet path '" << p.string() << "' is not a directory"); + } + } + utils::remove_duplicates_from_vector(cfg.graftlet_dirs); +} + +} + diff --git a/src/supernode/server/server.cpp b/src/supernode/server/server.cpp new file mode 100644 index 00000000..6e796c27 --- /dev/null +++ b/src/supernode/server/server.cpp @@ -0,0 +1,322 @@ + +#include "supernode/server/server.h" +#include "supernode/server/config.h" + +#include "backtrace.h" +#include "GraftletLoader.h" +#include "sys_info.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "supernode.server" + +namespace graft::supernode::server { + +static std::function int_handler, term_handler, hup_handler; + +static void signal_handler_shutdown(int sig_num) +{ + if(int_handler) int_handler(sig_num); +} + +static void signal_handler_terminate(int sig_num) +{ + if(term_handler) term_handler(sig_num); +} + +static void signal_handler_restart(int sig_num) +{ + if(hup_handler) hup_handler(sig_num); +} + +Server::Server() +{ +} + +Server::~Server() +{ +} + +bool Server::init(Config& cfg) +{ + create_looper(cfg); + create_system_info_counter(); + initGraftlets(); + add_global_ctx_cleaner(); + + initGlobalContext(); + + initMisc(cfg); + + initConnectionManagers(); + initRouters(); + initGraftletRouters(); + + for(auto& it : m_conManagers) + { + ConnectionManager& cm = *it.second.get(); + cm.enableRouting(); + utils::check_routes(cm); + cm.bind(*m_looper); + } + + return true; +} + +RunResult Server::run(void) +{ + initSignals(); + + auto res = RunResult::UnexpectedOk; + + //shutdown + int_handler = [this, &res](int sig_num) + { + LOG_PRINT_L0("Stopping server"); + stop(); + res = RunResult::SignalShutdown; + }; + + //terminate + term_handler = [this, &res](int sig_num) + { + LOG_PRINT_L0("Force stopping server"); + stop(true); + res = RunResult::SignalTerminate; + }; + + //restart + hup_handler = [this, &res](int sig_num) + { + LOG_PRINT_L0("Restarting server"); + stop(); + res = RunResult::SignalRestart; + }; + + serve(); + + return res; +} + +void Server::initSignals() +{ + struct sigaction sa; + + sigemptyset(&sa.sa_mask); + + sa.sa_sigaction = graft_bt_sighandler; + sa.sa_flags = SA_SIGINFO; + + ::sigaction(SIGSEGV, &sa, NULL); + + sa.sa_sigaction = NULL; + sa.sa_flags = 0; + sa.sa_handler = signal_handler_shutdown; + ::sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = signal_handler_terminate; + ::sigaction(SIGTERM, &sa, NULL); + + sa.sa_handler = signal_handler_restart; + ::sigaction(SIGHUP, &sa, NULL); +} + +Config& Server::config() +{ + assert(m_looper); + return m_looper->config(); +} + +bool Server::ready(void) const +{ + return m_looper && m_looper->ready(); +} + +void Server::stop(bool force) +{ + m_looper->stop(force); +} + +void Server::create_looper(Config& cfg) +{ + assert(!m_looper); + m_looper = std::make_unique(cfg); + assert(m_looper); +} + +void Server::create_system_info_counter(void) +{ + assert(!m_sys_info); + m_sys_info = std::make_unique(); + assert(m_sys_info); +} + +void Server::initGraftlets() +{ + if(m_graftletLoader) return; + + m_graftletLoader = std::make_unique(); + LOG_PRINT_L1("Searching graftlets"); + for(auto& it : config().graftlet_dirs) + { + LOG_PRINT_L1("Searching graftlets in directory '") << it << "'"; + m_graftletLoader->findGraftletsInDirectory(it, "so"); + } + m_graftletLoader->checkDependencies(); +} + +void Server::initGraftletRouters() +{ + ConnectionManager* cm = getConMgr("HTTP"); + assert(cm); + assert(m_graftletLoader); + IGraftlet::EndpointsVec endpoints = m_graftletLoader->getEndpoints(); + if(!endpoints.empty()) + { + Router graftlet_router; + for(const auto& item : endpoints) + { + const std::string& endpoint = std::get<0>(item); + const int& method = std::get<1>(item); + const Router::Handler& handler = std::get<2>(item); + + graftlet_router.addRoute(endpoint, method, {nullptr, handler , nullptr}); + } + cm->addRouter(graftlet_router); + } +} + +void Server::initGlobalContext() +{ +// TODO: why context intialized second time here? + //ANSWER: It is correct. The ctx is not initialized, ctx is attached to + // the global part of the context to which we want to get access here, only + // the local part of it has lifetime the same as the lifetime of ctx variable. + Context ctx(m_looper->gcm()); +// ctx.global["testnet"] = copts.testnet; +// ctx.global["watchonly_wallets_path"] = copts.watchonly_wallets_path; +// ctx.global["cryptonode_rpc_address"] = copts.cryptonode_rpc_address; + assert(m_sys_info); + ctx.runtime_sys_info(*(m_sys_info.get())); + ctx.config(m_looper->config()); +} + +void Server::serve() +{ + LOG_PRINT_L0("Starting server on: [http] " << config().http_address << ", [coap] " << config().coap_address); + m_looper->serve(); +} + +namespace details +{ + +void initGraftletDirs(const Config& cfg, const std::string& dirs_opt, bool dirs_opt_exists, std::vector& graftlet_dirs) +{//configOpts.graftlet_dirs + namespace fs = boost::filesystem; + + graftlet_dirs.clear(); + + fs::path self_dir = cfg.self_dir; + self_dir = self_dir.remove_filename(); + + if(!dirs_opt_exists) + { + graftlet_dirs.push_back(fs::complete("graftlets", self_dir).string()); + return; + } + + fs::path cur_dir(fs::current_path()); + + //if list empty then load none graftlet + if(dirs_opt.empty()) return; + + //split and fill set + std::set set; + for(std::string::size_type s = 0;;) + { + std::string::size_type e = dirs_opt.find(':',s); + if(e == std::string::npos) + { + set.insert(dirs_opt.substr(s)); + break; + } + set.insert(dirs_opt.substr(s,e-s)); + s = e + 1; + } + for(auto& it : set) + { + if(it.is_relative()) + { + bool found = false; + fs::path path1 = fs::complete(it, self_dir); + if(fs::is_directory(path1)) + { + graftlet_dirs.push_back(path1.string()); + found = true; + } + if(self_dir != cur_dir) + { + fs::path path2 = fs::complete(it, cur_dir); + if(fs::is_directory(path2)) + { + graftlet_dirs.push_back(path2.string()); + found = true; + } + } + if(!found) + { + LOG_PRINT_L1("Graftlet path '" << it.string() << "' is not a directory"); + } + } + else + { + if(fs::is_directory(it)) + graftlet_dirs.emplace_back(it.string()); + else + LOG_PRINT_L1("Graftlet path '" << it.string() << "' is not a directory"); + } + } + {//remove duplicated dirs + std::set set; + auto end = std::remove_if(graftlet_dirs.begin(), graftlet_dirs.end(), + [&set](auto& s)->bool{ return !set.emplace(s).second; } + ); + graftlet_dirs.erase(end, graftlet_dirs.end()); + } +} + +} //namespace details + + +ConnectionManager* Server::getConMgr(const ConnectionManager::Proto& proto) +{ + auto it = m_conManagers.find(proto); + assert(it != m_conManagers.end()); + return it->second.get(); +} + +void Server::initConnectionManagers() +{ + std::unique_ptr httpcm = std::make_unique(); + auto res1 = m_conManagers.emplace(httpcm->getProto(), std::move(httpcm)); + assert(res1.second); + std::unique_ptr coapcm = std::make_unique(); + auto res2 = m_conManagers.emplace(coapcm->getProto(), std::move(coapcm)); + assert(res2.second); +} + + +void Server::add_global_ctx_cleaner() +{ + auto cleaner = [](const graft::Router::vars_t& vars, const graft::Input& input, graft::Context& ctx, graft::Output& output)->graft::Status + { + graft::Context::GlobalFriend::cleanup(ctx.global); + return graft::Status::Ok; + }; + m_looper->addPeriodicTask( + graft::Router::Handler3(nullptr, cleaner, nullptr), + std::chrono::milliseconds(m_looper->config().lru_timeout_ms) + ); +} + +} + diff --git a/src/task.cpp b/src/task.cpp index 3e045678..9bdcc910 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -4,6 +4,7 @@ #include "state_machine.h" #include "handler_api.h" #include "sys_info.h" +#include "supernode/server/config.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "supernode.task" @@ -175,16 +176,26 @@ void StateMachine::init_table() } -TaskManager::TaskManager(const ConfigOpts& copts) : m_copts(copts), m_gcm(this) +TaskManager::TaskManager(const ConfigOpts& copts) +: m_copts(copts) +, m_gcm(this) { // TODO: validate options, throw exception if any mandatory options missing initThreadPool(copts.workers_count, copts.worker_queue_len); m_stateMachine = std::make_unique(); } -TaskManager::~TaskManager() +TaskManager::TaskManager(const Config& cfg) +: m_cfg(cfg) +, m_gcm(this) { + // TODO: validate options, throw exception if any mandatory options missing + initThreadPool(cfg.workers_count, cfg.worker_queue_len); + m_stateMachine = std::make_unique(); +} +TaskManager::~TaskManager(void) +{ } inline size_t TaskManager::next_pow2(size_t val)