7#include <boost/algorithm/string.hpp>
8#include <boost/asio/ip/tcp.hpp>
9#include <boost/asio/ip/host_name.hpp>
10#include <boost/program_options.hpp>
11#pragma GCC diagnostic push
12#pragma GCC diagnostic ignored "-Wunused-result"
13#include <boost/process/child.hpp>
14#pragma GCC diagnostic pop
15#include <boost/process/env.hpp>
16#include <boost/process/system.hpp>
17#include <boost/process/io.hpp>
18#include <boost/lexical_cast.hpp>
19#include <boost/filesystem.hpp>
20#include <boost/filesystem/path.hpp>
21#include <boost/filesystem/fstream.hpp>
30#include <netinet/in.h>
38namespace bp = boost::process;
39namespace bpo = boost::program_options;
40using boost::asio::ip::tcp;
41using boost::asio::ip::host_name;
42using bpo::options_description;
43using bpo::variables_map;
46using namespace sysio::launcher::config;
56 names.push_back (
"localhost");
57 names.push_back (
"127.0.0.1");
59 boost::system::error_code ec;
60 string hn = host_name (ec);
61 if (ec.value() != boost::system::errc::success) {
62 cerr <<
"unable to retrieve host name: " << ec.message() << endl;
66 if (hn.find (
'.') != string::npos) {
67 names.push_back (hn.substr (0,hn.find(
'.')));
72 if (::getifaddrs (&ifap) == 0) {
73 for (ifaddrs *p_if = ifap; p_if != 0; p_if = p_if->ifa_next) {
74 if (p_if->ifa_addr != 0 &&
75 p_if->ifa_addr->sa_family == AF_INET &&
76 (p_if->ifa_flags & IFF_UP) == IFF_UP) {
77 sockaddr_in *ifaddr =
reinterpret_cast<sockaddr_in *
>(p_if->ifa_addr);
78 int32_t in_addr = ntohl(ifaddr->sin_addr.s_addr);
82 addrs.push_back (ifa);
89 cerr <<
"unable to query local ip interfaces" << endl;
97 for (
const auto &
a :
addrs) {
104 for (
const auto& n :
names) {
119 : genesis(
"genesis.json"),
123 host_name(
"127.0.0.1"),
164 return local_id.contains( host_name );
168 if (dot_label_str.empty() ) {
171 return dot_label_str;
178 string dot_label_str;
223 if (dot_label_str.empty() ) {
226 return dot_label_str;
230 return name.substr(
name.length() - 2 );
233 string dot_label_str;
249 dot_label_str =
name +
"\\nprod=";
251 dot_label_str +=
"<none>";
254 bool docomma =
false;
257 dot_label_str +=
",";
260 dot_label_str += prod;
342 static string producer_name(
unsigned int producer_number,
bool shared_producer =
false);
344 static const int total_chars = 12;
345 static const char slot_chars[];
346 static const char valid_char_range;
349const char producer_names::slot_chars[] =
"abcdefghijklmnopqrstuvwxyz";
350const char producer_names::valid_char_range =
sizeof(producer_names::slot_chars) - 1;
357 char prod_name[] =
"defproducera";
358 if (producer_number > valid_char_range) {
359 for (
int current_char_loc = 5; current_char_loc < total_chars; ++current_char_loc) {
360 prod_name[current_char_loc] = slot_chars[0];
364 prod_name[total_chars] =
'\0';
365 for (
int current_char_loc = total_chars - 1; current_char_loc >= 0; --current_char_loc) {
366 const unsigned int slot_value =
static_cast<char>(producer_number % valid_char_range);
367 producer_number /= valid_char_range;
368 prod_name[current_char_loc] = slot_chars[slot_value];
369 if (!producer_number)
374 if (
string(prod_name) ==
"defproducera" && producer_number != 0)
375 throw std::runtime_error(
"launcher not designed to handle numbers this large " );
377 if (shared_producer) {
442 const bfs::path &destination);
452 size_t skip_ndx (
size_t from,
size_t offset);
459 void format_ssh (
const string &cmd,
const string &host_name,
string &ssh_cmd_line);
460 void do_command(
const host_def& host,
const string&
name, vector<pair<string, string>> env_pairs,
const string& cmd);
461 bool do_ssh (
const string &cmd,
const string &host_name);
467 vector<pair<host_def, eosd_def>>
get_nodes(
const string& node_number_list);
468 void bounce (
const string& node_numbers);
469 void down (
const string& node_numbers);
470 void roll (
const string& host_names);
479 (
"force,f", bpo::bool_switch(&
force_overwrite)->default_value(
false),
"Force overwrite of existing configuration files and erase blockchain")
480 (
"nodes,n",bpo::value<size_t>(&
total_nodes)->default_value(1),
"total number of nodes to configure and launch")
481 (
"unstarted-nodes",bpo::value<size_t>(&
unstarted_nodes)->default_value(0),
"total number of nodes to configure, but not launch")
482 (
"pnodes,p",bpo::value<size_t>(&
prod_nodes)->default_value(1),
"number of nodes that contain one or more producers")
483 (
"producers",bpo::value<size_t>(&
producers)->default_value(21),
"total number of non-bios and non-shared producer instances in this network")
484 (
"shared-producers",bpo::value<size_t>(&
shared_producers)->default_value(0),
"total number of shared producers on each non-bios nodes")
485 (
"mode,m",bpo::value<vector<string>>()->multitoken()->default_value({
"any"},
"any"),
"connection mode, combination of \"any\", \"producers\", \"specified\", \"none\"")
486 (
"shape,s",bpo::value<string>(&
shape)->default_value(
"star"),
"network topology, use \"star\", \"mesh\", \"ring\", \"line\" or give a filename for custom")
487 (
"genesis,g",bpo::value<string>()->default_value(
"./genesis.json"),
"set the path to genesis.json")
488 (
"skip-signature", bpo::bool_switch(&
skip_transaction_signatures)->default_value(
false), (
string(node_executable_name) +
" does not require transaction signatures.").c_str())
489 (node_executable_name, bpo::value<string>(&
eosd_extra_args), (
"forward " + string(node_executable_name) +
" command line argument(s) to each instance of " + string(node_executable_name) +
", enclose arg(s) in quotes").c_str())
490 (
"specific-num", bpo::value<vector<uint>>()->composing(), (
"forward " +
string(node_executable_name) +
" command line argument(s) (using \"--specific-" +
string(node_executable_name) +
"\" flag) to this specific instance of " +
string(node_executable_name) +
". This parameter can be entered multiple times and requires a paired \"--specific-" +
string(node_executable_name) +
"\" flag each time it is used").c_str())
491 ((
"specific-" + string(node_executable_name)).c_str(), bpo::value<vector<string>>()->composing(), (
"forward " +
string(node_executable_name) +
" command line argument(s) to its paired specific instance of " +
string(node_executable_name) +
"(using \"--specific-num\"), enclose arg(s) in quotes").c_str())
492 (
"spcfc-inst-num", bpo::value<vector<uint>>()->composing(), (
"Specify a specific version installation path (using \"--spcfc-inst-"+
string(node_executable_name) +
"\" flag) for launching this specific instance of " +
string(node_executable_name) +
". This parameter can be entered multiple times and requires a paired \"--spcfc-inst-" +
string(node_executable_name) +
"\" flag each time it is used").c_str())
493 ((
"spcfc-inst-" + string(node_executable_name)).c_str(), bpo::value<vector<string>>()->composing(), (
"Provide a specific version installation path to its paired specific instance of " +
string(node_executable_name) +
"(using \"--spcfc-inst-num\")").c_str())
494 (
"delay,d",bpo::value<int>(&
start_delay)->default_value(0),
"seconds delay before starting each node after the first")
495 (
"boot",bpo::bool_switch(&
boot)->default_value(
false),
"After deploying the nodes and generating a boot script, invoke it.")
496 (
"nogen",bpo::bool_switch(&
nogen)->default_value(
false),
"launch nodes without writing new config files")
497 (
"host-map",bpo::value<string>(),
"a file containing mapping specific nodes to hosts. Used to enhance the custom shape argument")
498 (
"servers",bpo::value<string>(),
"a file containing ip addresses and names of individual servers to deploy as producers or non-producers ")
499 (
"per-host",bpo::value<int>(&
per_host)->default_value(0),(
"specifies how many " +
string(node_executable_name) +
" instances will run on a single host. Use 0 to indicate all on one.").c_str())
500 (
"network-name",bpo::value<string>(&network.name)->default_value(
"testnet_"),
"network name prefix used in GELF logging source")
501 (
"enable-gelf-logging",bpo::value<bool>(&
gelf_enabled)->default_value(
false),
"enable gelf logging appender in logging configuration file")
502 (
"gelf-endpoint",bpo::value<string>(&
gelf_endpoint)->default_value(
"10.160.11.21:12201"),
"hostname:port or ip:port of GELF endpoint")
503 (
"template",bpo::value<string>(&
start_temp)->default_value(
"testnet.template"),
"the startup script template")
504 (
"script",bpo::value<string>(&
start_script)->default_value(
"bios_boot.sh"),
"the generated startup script name")
505 (
"max-block-cpu-usage",bpo::value<uint32_t>(),
"Provide the \"max-block-cpu-usage\" value to use in the genesis.json file")
506 (
"max-transaction-cpu-usage",bpo::value<uint32_t>(),
"Provide the \"max-transaction-cpu-usage\" value to use in the genesis.json file")
510template<class enum_type, class=typename std::enable_if<std::is_enum<enum_type>::value>::type>
511inline enum_type&
operator|=(enum_type&lhs,
const enum_type& rhs)
513 using T = std::underlying_type_t <enum_type>;
514 return lhs =
static_cast<enum_type
>(
static_cast<T>(lhs) |
static_cast<T>(rhs));
519 if (vmap.count(num_selector)) {
520 const auto specific_nums = vmap[num_selector].as<vector<uint>>();
521 const auto specific_args = vmap[paired_selector].as<vector<string>>();
522 if (specific_nums.size() != specific_args.size()) {
523 cerr <<
"ERROR: every " << num_selector <<
" argument must be paired with a " << paired_selector <<
" argument" << endl;
526 const auto total_nodes = vmap[
"nodes"].as<
size_t>();
527 for(uint i = 0; i < specific_nums.size(); ++i)
529 const auto& num = specific_nums[i];
530 if (num >= total_nodes) {
531 cerr <<
"\"--" << num_selector <<
"\" provided value= " << num <<
" is higher than \"--nodes\" provided value=" << total_nodes << endl;
534 selector_map[num] = specific_args[i];
541 if (vmap.count(
"mode")) {
542 const vector<string> modes = vmap[
"mode"].as<vector<string>>();
543 for(
const string&m : modes)
545 if (boost::iequals(m,
"any"))
547 else if (boost::iequals(m,
"producers"))
549 else if (boost::iequals(m,
"specified"))
551 else if (boost::iequals(m,
"none"))
554 cerr <<
"unrecognized connection mode: " << m << endl;
560 if (vmap.count(
"max-block-cpu-usage")) {
564 if (vmap.count(
"max-transaction-cpu-usage")) {
568 genesis = vmap[
"genesis"].as<
string>();
569 if (vmap.count(
"host-map")) {
572 if (vmap.count(
"servers")) {
579 using namespace std::chrono;
580 system_clock::time_point now = system_clock::now();
581 std::time_t now_c = system_clock::to_time_t(now);
583 dstrm << std::put_time(std::localtime(&now_c),
"%Y_%m_%d_%H_%M_%S");
586 if ( ! (
shape.empty() ||
587 boost::iequals(
shape,
"ring" ) ||
588 boost::iequals(
shape,
"line" ) ||
589 boost::iequals(
shape,
"star" ) ||
590 boost::iequals(
shape,
"mesh" )) &&
592 bfs::path src =
shape;
600 for (
auto &eosd : binding.instances) {
601 eosd.host = binding.host_name;
602 eosd.p2p_endpoint = binding.public_name +
":" + boost::lexical_cast<string,uint16_t>(eosd.p2p_port);
624 cerr <<
"ERROR: if provided, \"--nodes\" must be equal or greater than the number of nodes indicated by \"--pnodes\" and \"--unstarted-nodes\"." << endl;
628 if (vmap.count(
"specific-num")) {
629 const auto specific_nums = vmap[
"specific-num"].as<vector<uint>>();
630 const auto specific_args = vmap[
"specific-" + string(node_executable_name)].as<vector<string>>();
631 if (specific_nums.size() != specific_args.size()) {
632 cerr <<
"ERROR: every specific-num argument must be paired with a specific-" << node_executable_name <<
" argument" << endl;
637 for(uint i = 0; i < specific_nums.size(); ++i)
639 const auto& num = specific_nums[i];
640 if (num >= allowed_nums) {
641 cerr <<
"\"--specific-num\" provided value= " << num <<
" is higher than \"--nodes\" provided value=" <<
total_nodes << endl;
648 char* erd_env_var = getenv (
"SYSIO_HOME");
649 if (erd_env_var ==
nullptr || std::string(erd_env_var).empty()) {
650 erd_env_var = getenv (
"PWD");
653 if (erd_env_var !=
nullptr) {
660 if (!bfs::exists(
stage)) {
661 cerr <<
"\"" <<
erd <<
"\" is not a valid path. Please ensure environment variable SYSIO_HOME is set to the build path." << endl;
664 stage /= bfs::path(
"staging");
665 bfs::create_directories (
stage);
700 string node_cfg_name;
704 node_cfg_name =
"node_bios";
708 dex += boost::lexical_cast<string,int>(
next_node++);
709 node.
name = network.name + dex;
710 node_cfg_name =
"node_" + dex;
719 if (boost::iequals (
shape,
"ring")) {
722 else if (boost::iequals (
shape,
"line")) {
725 else if (boost::iequals (
shape,
"star")) {
728 else if (boost::iequals (
shape,
"mesh")) {
739 for (
auto &node : network.nodes) {
747 if (!output.empty()) {
748 bfs::path savefile =
output;
750 bfs::ofstream sf (savefile);
755 savefile = bfs::path (output.stem().string() +
"_hosts.json");
762 bfs::ofstream sf (savefile);
775 bfs::ofstream df (
"testnet.dot");
776 df <<
"digraph G\n{\nlayout=\"circo\";\n";
777 for (
auto &node : network.nodes) {
778 for (
const auto &
p : node.second.peers) {
779 string pname=network.nodes.find(
p)->second.instance->dot_label();
780 df <<
"\"" << node.second.instance->dot_label ()
782 <<
"\" [dir=\"forward\"];" << std::endl;
794 local_host.
genesis = genesis.string();
799 eosd.
set_host (&local_host, i == 0);
800 local_host.
instances.emplace_back(move(eosd));
802 bindings.emplace_back(move(local_host));
811 bool do_bios =
false;
814 bindings.emplace_back(move(*lhost));
818 lhost->
genesis = genesis.string();
819 if (host_ndx < num_prod_addr ) {
825 else if (host_ndx - num_prod_addr < num_nonprod_addr) {
826 size_t ondx = host_ndx - num_prod_addr;
833 string ext = host_ndx < 10 ?
"0" :
"";
834 ext += boost::lexical_cast<string,int>(host_ndx);
851 lhost->
instances.emplace_back(move(eosd));
854 bindings.emplace_back( move(*lhost) );
863 cerr <<
"Unable to allocate producers due to insufficient prod_nodes = " <<
prod_nodes <<
"\n";
870 unsigned int producer_number = 0;
873 for (
auto &inst : h.instances) {
874 bool is_bios = inst.name ==
"bios";
876 node.
name = inst.name;
879 private_key_type(
string(
"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3")) :
880 private_key_type::generate();
881 auto pubkey = kp.get_public_key();
882 node.
keys.emplace_back (move(kp));
884 string prodname =
"sysio";
890 int count = per_node;
910 network.nodes[node.
name] = move(node);
911 inst.node = &network.nodes[inst.name];
922 if (h.host_name ==
name) {
928 cerr <<
"could not find host for " <<
name << endl;
939 if ((h.host_name == host_id) || (h.public_name == host_id)) {
945 cerr <<
"could not find host for " << host_id << endl;
953 boost::system::error_code ec;
961 if (host->is_local()) {
962 bfs::path cfgdir = bfs::path(host->sysio_home) / instance.
config_dir_name;
963 bfs::path dd = bfs::path(host->sysio_home) / instance.
data_dir_name;
965 if (!bfs::exists (cfgdir)) {
966 if (!bfs::create_directories (cfgdir, ec) && ec.value()) {
968 <<
" errno " << ec.value() <<
" " << strerror(ec.value()) << endl;
973 cerr << cfgdir /
"config.ini" <<
" exists. Use -f|--force to overwrite configuration\n";
977 if (!bfs::exists (dd)) {
978 if (!bfs::create_directories (dd, ec) && ec.value()) {
980 <<
" errno " << ec.value() <<
" " << strerror(ec.value()) << endl;
986 if (ec.value() != 0) {
987 cerr <<
"count = " <<
count <<
" could not remove old directory: " << dd
988 <<
" " << strerror(ec.value()) << endl;
992 if (ec.value() != 0) {
993 cerr <<
"count = " <<
count <<
" could not remove old directory: " << dd
994 <<
" " << strerror(ec.value()) << endl;
1000 cerr << dd <<
". Use -f|--force to erase blockchain data" << endl;
1004 bfs::copy_file (genesis_source, cfgdir /
"genesis.json", bfs::copy_option::overwrite_if_exists);
1005 bfs::copy_file (logging_source, cfgdir /
"logging.json", bfs::copy_option::overwrite_if_exists);
1006 bfs::copy_file (
source, cfgdir /
"config.ini", bfs::copy_option::overwrite_if_exists);
1011 bfs::path rfile = bfs::path (host->sysio_home) / instance.
config_dir_name /
"config.ini";
1014 cerr <<
"cmdline = " << scp_cmd_line << endl;
1015 int res = boost::process::system (scp_cmd_line);
1017 cerr <<
"unable to scp config file to host " << host->host_name << endl;
1021 rfile = bfs::path (host->sysio_home) / instance.
config_dir_name /
"logging.json";
1025 res = boost::process::system (scp_cmd_line);
1027 cerr <<
"unable to scp logging config file to host " << host->host_name << endl;
1031 rfile = bfs::path (host->sysio_home) / instance.
config_dir_name /
"genesis.json";
1035 res = boost::process::system (scp_cmd_line);
1037 cerr <<
"unable to scp genesis.json file to host " << host->host_name << endl;
1046 string scp_cmd_line = network.ssh_helper.scp_cmd +
" ";
1047 const string &args = host.ssh_args.length() ? host.ssh_args : network.ssh_helper.ssh_args;
1048 if (args.length()) {
1049 scp_cmd_line += args +
" ";
1051 scp_cmd_line +=
source.string() +
" ";
1053 const string &uid = host.ssh_identity.length() ? host.ssh_identity : network.ssh_helper.ssh_identity;
1055 scp_cmd_line += uid +
"@";
1058 scp_cmd_line += host.host_name +
":" + destination.string();
1060 return scp_cmd_line;
1065 bool is_bios = (node.
name ==
"bios");
1071 if (!bfs::exists(dd)) {
1073 bfs::create_directories (dd);
1074 }
catch (
const bfs::filesystem_error &ex) {
1075 cerr <<
"write_config_files threw " << ex.what() << endl;
1080 filename = dd /
"config.ini";
1082 bfs::ofstream cfg(filename);
1084 cerr <<
"unable to open " << filename <<
" " << strerror(errno) <<
"\n";
1088 cfg <<
"blocks-dir = " <<
block_dir <<
"\n";
1089 cfg <<
"http-server-address = " << host->host_name <<
":" << instance.
http_port <<
"\n";
1090 cfg <<
"http-validate-host = false\n";
1091 cfg <<
"p2p-listen-endpoint = " << host->listen_addr <<
":" << instance.
p2p_port <<
"\n";
1092 cfg <<
"p2p-server-address = " << host->public_name <<
":" << instance.
p2p_port <<
"\n";
1096 cfg <<
"enable-stale-production = true\n";
1099 cfg <<
"allowed-connection = any\n";
1102 cfg <<
"allowed-connection = none\n";
1107 cfg <<
"allowed-connection = producers\n";
1110 cfg <<
"allowed-connection = specified\n";
1111 cfg <<
"peer-key = \"" << node.
keys.begin()->get_public_key().to_string() <<
"\"\n";
1112 cfg <<
"peer-private-key = [\"" << node.
keys.begin()->get_public_key().to_string()
1113 <<
"\",\"" << node.
keys.begin()->to_string() <<
"\"]\n";
1118 auto &bios_node = network.nodes[
"bios"];
1119 cfg <<
"p2p-peer-address = " << bios_node.instance->p2p_endpoint<<
"\n";
1121 for (
const auto &
p : node.
peers) {
1122 cfg <<
"p2p-peer-address = " << network.nodes.find(
p)->second.instance->p2p_endpoint <<
"\n";
1125 for (
const auto &kp : node.
keys ) {
1126 cfg <<
"private-key = [\"" << kp.get_public_key().to_string()
1127 <<
"\",\"" << kp.to_string() <<
"\"]\n";
1130 cfg <<
"producer-name = " <<
p <<
"\n";
1132 cfg <<
"plugin = sysio::producer_plugin\n";
1134 cfg <<
"plugin = sysio::net_plugin\n";
1135 cfg <<
"plugin = sysio::chain_api_plugin\n";
1145 if (!bfs::exists(dd)) {
1146 bfs::create_directories(dd);
1149 filename = dd /
"logging.json";
1151 bfs::ofstream cfg(filename);
1153 cerr <<
"unable to open " << filename <<
" " << strerror(errno) <<
"\n";
1159 log_config.appenders.push_back(
1163 (
"host", instance.
name )
1165 log_config.loggers.front().appenders.push_back(
"net" );
1172 log_config.loggers.emplace_back( p2p );
1176 http.appenders.push_back(
"stderr" );
1178 log_config.loggers.emplace_back( http );
1184 log_config.loggers.emplace_back( pp );
1190 log_config.loggers.emplace_back( tt );
1196 log_config.loggers.emplace_back( tft );
1202 log_config.loggers.emplace_back( tts );
1208 log_config.loggers.emplace_back( ttf );
1214 log_config.loggers.emplace_back( ta );
1217 cfg.write( str.c_str(), str.size() );
1223 const bfs::path genesis_path = genesis.is_complete() ?
genesis : bfs::current_path() /
genesis;
1224 if (!bfs::exists(genesis_path)) {
1225 cout <<
"generating default genesis file " << genesis_path << endl;
1229 string bioskey = network.nodes[
"bios"].keys[0].get_public_key().to_string();
1245 if (!bfs::exists(dd)) {
1246 bfs::create_directories(dd);
1249 filename = dd /
"genesis.json";
1255 bfs::path filename = bfs::current_path() /
"setprods.json";
1256 bfs::ofstream psfile (filename);
1257 if(!psfile.good()) {
1258 cerr <<
"unable to open " << filename <<
" " << strerror(errno) <<
"\n";
1263 if (
p.producer_name !=
"sysio")
1267 psfile.write( str.c_str(), str.size() );
1279 bfs::ofstream brb (bfs::current_path() /
start_script);
1281 cerr <<
"unable to open " << bfs::current_path() <<
"/" <<
start_script <<
" " << strerror(errno) <<
"\n";
1285 auto &bios_node = network.nodes[
"bios"];
1286 uint16_t biosport = bios_node.instance->http_port;
1287 string bhost = bios_node.instance->host;
1289 string prefix =
"###INSERT ";
1290 size_t len = prefix.length();
1291 while (getline(src,line)) {
1292 if (line.substr(0,
len) == prefix) {
1293 string key = line.substr(
len);
1294 if (key ==
"envars") {
1295 brb <<
"bioshost=" << bhost <<
"\nbiosport=" << biosport <<
"\n";
1297 else if (key ==
"prodkeys" ) {
1298 for (
auto &node : network.nodes) {
1299 brb <<
"wcmd import -n ignition --private-key " << node.second.keys[0].to_string() <<
"\n";
1302 else if (key ==
"cacmd") {
1304 if (
p.producer_name ==
"sysio") {
1307 brb <<
"cacmd " <<
p.producer_name
1308 <<
" " <<
p.block_signing_key.to_string() <<
" " <<
p.block_signing_key.to_string() <<
"\n";
1312 brb << line <<
"\n";
1319 return aliases[ndx] ==
"bios";
1342 while (--attempts && (
is_bios_ndx(ndx) || ndx == from)) {
1355 bool end_of_loop =
false;
1360 if (end_of_loop && !make_ring) {
1363 network.nodes.find(
aliases[i])->second.peers.push_back (
aliases[front]);
1366 else if (non_bios == 2) {
1370 network.nodes.find(
aliases[n0])->second.peers.push_back (
aliases[n1]);
1372 network.nodes.find(
aliases[n1])->second.peers.push_back (
aliases[n0]);
1388 if (non_bios > 12) {
1389 links =
static_cast<size_t>(sqrt(non_bios)) + 2;
1391 size_t gap = non_bios > 6 ? 3 : (non_bios - links)/2 +1;
1392 while (non_bios % gap == 0) {
1396 std::map <string, std::set<string>> peers_to_from;
1399 const auto& iter = network.nodes.find(
aliases[i]);
1400 auto ¤t = iter->second;
1401 const auto& current_name = iter->first;
1403 for (
size_t l = 1;
l <= links;
l++) {
1406 for (
bool found =
true; found; ) {
1408 for (
auto &
p : current.peers) {
1421 if (peers_to_from[peer].
count(current_name) < 2) {
1422 current.peers.push_back(peer);
1424 peers_to_from[current_name].insert(peer);
1435 std::map <string, std::set<string>> peers_to_from;
1438 const auto& iter = network.nodes.find(
aliases[i]);
1439 auto ¤t = iter->second;
1440 const auto& current_name = iter->first;
1442 for (
size_t j = 1;
j < non_bios;
j++) {
1444 const auto& peer =
aliases[ndx];
1446 if (peers_to_from[peer].
count(current_name) < 2) {
1447 current.peers.push_back (peer);
1449 peers_to_from[current_name].insert(peer);
1460 for (
auto &inst : h.instances) {
1473 const string &host_name,
1474 string & ssh_cmd_line) {
1476 ssh_cmd_line = network.ssh_helper.ssh_cmd +
" ";
1477 if (network.ssh_helper.ssh_args.length()) {
1478 ssh_cmd_line += network.ssh_helper.ssh_args +
" ";
1480 if (network.ssh_helper.ssh_identity.length()) {
1481 ssh_cmd_line += network.ssh_helper.ssh_identity +
"@";
1483 ssh_cmd_line += host_name +
" \"" + cmd +
"\"";
1484 cerr <<
"cmdline = " << ssh_cmd_line << endl;
1489 string ssh_cmd_line;
1491 int res = boost::process::system (ssh_cmd_line);
1497 bfs::path abs_config_dir = bfs::path(host->sysio_home) / node.
config_dir_name;
1498 bfs::path abs_data_dir = bfs::path(host->sysio_home) / node.
data_dir_name;
1500 string acd = abs_config_dir.string();
1501 string add = abs_data_dir.string();
1502 string cmd =
"cd " + host->sysio_home;
1504 cmd =
"cd " + host->sysio_home;
1505 if (!
do_ssh(cmd, host->host_name)) {
1506 cerr <<
"Unable to switch to path " << host->sysio_home
1507 <<
" on host " << host->host_name << endl;
1512 if (!
do_ssh(cmd,host->host_name)) {
1513 cmd =
"mkdir -p " + acd;
1514 if (!
do_ssh (cmd, host->host_name)) {
1515 cerr <<
"Unable to invoke " << cmd <<
" on host " << host->host_name << endl;
1520 if (
do_ssh(cmd,host->host_name)) {
1524 if (!
do_ssh (cmd, host->host_name)) {
1525 cerr <<
"Unable to remove old data directories on host "
1526 << host->host_name << endl;
1531 cerr <<
add <<
" already exists on host " << host->host_name <<
". Use -f/--force to overwrite configuration and erase blockchain" << endl;
1536 cmd =
"mkdir -p " +
add;
1537 if (!
do_ssh (cmd, host->host_name)) {
1538 cerr <<
"Unable to invoke " << cmd <<
" on host "
1539 << host->host_name << endl;
1547 if (args.empty() || arg.empty())
1550 string left, middle, right;
1551 size_t found = args.find(arg);
1552 if (found != std::string::npos){
1553 left = args.substr(0, found);
1554 middle = args.substr(found + 2);
1555 found = middle.find(
"--");
1556 if (found != std::string::npos){
1557 right = middle.substr(found);
1559 return left + right;
1568 bfs::path reout = dd /
"stdout.txt";
1569 bfs::path reerr_sl = dd /
"stderr.txt";
1570 bfs::path reerr_base = bfs::path(
"stderr." +
launch_time +
".txt");
1571 bfs::path reerr = dd / reerr_base;
1572 bfs::path pidf = dd / bfs::path(
string(node_executable_name) +
".pid");
1576 }
catch (
const bfs::filesystem_error &ex) {
1577 cerr <<
"deploy_config_files threw " << ex.what() << endl;
1582 info.
remote = !host->is_local();
1584 string install_path;
1586 const auto node_num = boost::lexical_cast<uint16_t,string>(instance.
get_node_num());
1591 string eosdcmd = install_path +
"programs/nodeop/" + string(node_executable_name) +
" ";
1593 eosdcmd +=
"--skip-transaction-signatures ";
1599 const auto node_num = boost::lexical_cast<uint16_t,string>(instance.
get_node_num());
1606 eosdcmd +=
"--enable-stale-production true ";
1611 eosdcmd +=
" --genesis-json " + instance.
config_dir_name +
"/genesis.json";
1613 eosdcmd +=
" --genesis-timestamp " + gts;
1616 if (eosdcmd.find(
"sysio::history_api_plugin") != string::npos && eosdcmd.find(
"sysio::trace_api_plugin") != string::npos){
1623 if (!host->is_local()) {
1625 cerr <<
"Unable to use \"unstarted-nodes\" with a remote hose" << endl;
1628 string cmdl (
"cd ");
1629 cmdl += host->sysio_home +
"; nohup " + eosdcmd +
" > "
1630 + reout.string() +
" 2> " + reerr.string() +
"& echo $! > " + pidf.string()
1631 +
"; rm -f " + reerr_sl.string()
1632 +
"; ln -s " + reerr_base.string() +
" " + reerr_sl.string();
1633 if (!
do_ssh (cmdl, host->host_name)){
1634 cerr <<
"Unable to invoke " << cmdl
1635 <<
" on host " << host->host_name << endl;
1639 string cmd =
"cd " + host->sysio_home +
"; kill -15 $(cat " + pidf.string() +
")";
1640 format_ssh (cmd, host->host_name, info.kill_cmd);
1643 cerr <<
"spawning child, " << eosdcmd << endl;
1645 bp::child c(eosdcmd, bp::std_out > reout, bp::std_err > reerr );
1646 bfs::remove(reerr_sl);
1647 bfs::create_symlink (reerr_base, reerr_sl);
1649 bfs::ofstream pidout (pidf);
1650 pidout << c.id() << flush;
1653 info.pid_file = pidf.string();
1657 cerr <<
"child not running after spawn " << eosdcmd << endl;
1658 for (
int i = 0; i > 0; i++) {
1659 if (c.running () )
break;
1665 cerr <<
"not spawning child, " << eosdcmd << endl;
1668 const bfs::path start_file = dd /
"start.cmd";
1669 bfs::ofstream sf (start_file);
1671 sf << eosdcmd << endl;
1679launcher_def::kill_instance(
eosd_def,
string sig_opt) {
1692 cerr <<
"feature not yet implemented " << endl;
1695 kill_instance (node.second.instance, sig_opt);
1702 bfs::path
source =
"last_run.json";
1707 (!info.remote && mode ==
LM_LOCAL) ) {
1709 if( info.pid_file.length() ) {
1712 string kill_cmd =
"kill " + sig_opt +
" " + pid;
1713 boost::process::system( kill_cmd );
1715 boost::process::system( info.kill_cmd );
1719 }
catch( std::exception& stde ) {
1720 cerr <<
"unable to kill std::exception=" << stde.what() << endl;
1722 cerr <<
"Unable to kill" << endl;
1728 }
catch( std::exception& stde ) {
1729 cerr <<
"unable to open " <<
source <<
" std::exception=" << stde.what() << endl;
1731 cerr <<
"Unable to open " <<
source << endl;
1739 string node_num_str = node_num < 10 ?
"0":
"";
1740 node_num_str += boost::lexical_cast<string,uint16_t>(node_num);
1741 return node_num_str;
1744pair<host_def, eosd_def>
1746 const string node_name = network.name +
get_node_num(node_num);
1748 for (
const auto& node: host.instances) {
1749 if (node_name == node.name) {
1750 return make_pair(host, node);
1754 cerr <<
"Unable to find node " << node_num << endl;
1758vector<pair<host_def, eosd_def>>
1760 vector<pair<host_def, eosd_def>> node_list;
1763 for (
auto node: host.instances) {
1764 cout <<
"host=" << host.host_name <<
", node=" << node.name << endl;
1765 node_list.push_back(make_pair(host, node));
1770 vector<string> nodes;
1771 boost::split(nodes, node_number_list, boost::is_any_of(
","));
1772 for (
string node_number: nodes) {
1775 node = boost::lexical_cast<uint16_t,string>(node_number);
1777 catch(boost::bad_lexical_cast &) {
1780 if (node < 0 || node > 99) {
1781 cerr <<
"Bad node number found in node number list: " << node_number << endl;
1792 vector<pair<string, string>> env_pairs,
const string& cmd) {
1793 if (!host.is_local()) {
1794 string rcmd =
"cd " + host.sysio_home +
"; ";
1795 for (
auto& env_pair : env_pairs) {
1796 rcmd +=
"export " + env_pair.first +
"=" + env_pair.second +
"; ";
1799 if (!
do_ssh(rcmd, host.host_name)) {
1800 cerr <<
"Remote command failed for " <<
name << endl;
1806 for (
auto& env_pair : env_pairs) {
1807 e.emplace(env_pair.first, env_pair.second);
1809 bp::child c(cmd, e);
1816 auto node_list =
get_nodes(node_numbers);
1817 for (
auto node_pair: node_list) {
1818 const host_def& host = node_pair.first;
1819 const eosd_def& node = node_pair.second;
1821 cout <<
"Bouncing " << node.
name << endl;
1824 const auto node_num_i = boost::lexical_cast<uint16_t,string>(node_num);
1830 do_command(host, node.
name, { {
"SYSIO_HOME", host.sysio_home }, {
"SYSIO_NODE", node_num } }, cmd);
1836 auto node_list =
get_nodes(node_numbers);
1837 for (
auto node_pair: node_list) {
1838 const host_def& host = node_pair.first;
1839 const eosd_def& node = node_pair.second;
1841 cout <<
"Taking down " << node.
name << endl;
1842 string cmd =
"./scripts/sysio-tn_down.sh ";
1844 { {
"SYSIO_HOME", host.sysio_home }, {
"SYSIO_NODE", node_num }, {
"SYSIO_TN_RESTART_CONFIG_DIR", node.config_dir_name } },
1851 vector<string> hosts;
1852 boost::split(hosts, host_names, boost::is_any_of(
","));
1853 for (
string host_name: hosts) {
1854 cout <<
"Rolling " << host_name << endl;
1856 string cmd =
"./scripts/sysio-tn_roll.sh ";
1857 do_command(*host, host_name, { {
"SYSIO_HOME", host->sysio_home } }, cmd);
1864 cerr <<
"Invoking the blockchain boot script, " <<
start_script <<
"\n";
1866 bp::child c(script);
1868 cerr <<
"waiting for script completion\n";
1870 }
catch (bfs::filesystem_error &ex) {
1871 cerr <<
"wait threw error " << ex.what() <<
"\n";
1877 cerr <<
"**********************************************************************\n"
1878 <<
"run 'bash " <<
start_script <<
"' to kick off delegated block production\n"
1879 <<
"**********************************************************************\n";
1896 launch(*node->second.instance, gts);
1899 }
catch (std::exception& stde) {
1900 cerr <<
"unable to launch " <<
launch_name <<
" std::exception=" << stde.what() << endl;
1902 cerr <<
"Unable to launch " <<
launch_name << endl;
1914 for (
auto &inst : h.instances) {
1916 cerr <<
"launching " << inst.name << endl;
1919 cerr <<
"unable to launch " << inst.name <<
" fc::exception=" << fce.
to_detail_string() << endl;
1920 }
catch (std::exception& stde) {
1921 cerr <<
"unable to launch " << inst.name <<
" std::exception=" << stde.what() << endl;
1923 cerr <<
"unable to launch " << inst.name << endl;
1932 bfs::path savefile =
"last_run.json";
1933 bfs::ofstream sf (savefile);
1942 bfs::path parent = cfg_file.parent_path();
1943 if (parent.empty()) {
1946 if(!bfs::exists(parent)) {
1948 bfs::create_directories(parent);
1949 }
catch (bfs::filesystem_error &ex) {
1950 cerr <<
"could not create new directory: " << cfg_file.parent_path()
1951 <<
" caught " << ex.what() << endl;
1956 std::ofstream out_cfg( bfs::path(cfg_file).make_preferred().
string());
1957 for(
const boost::shared_ptr<bpo::option_description>& od : cfg.options())
1959 if(!od->description().empty()) {
1960 out_cfg <<
"# " << od->description() << std::endl;
1963 if(!od->semantic()->apply_default(store))
1964 out_cfg <<
"# " << od->long_name() <<
" = " << std::endl;
1966 auto example = od->format_parameter();
1969 out_cfg << od->long_name() <<
" = " <<
"false" << std::endl;
1972 example.erase(0, 6);
1973 example.erase(example.length()-1);
1974 out_cfg << od->long_name() <<
" = " << example << std::endl;
1977 out_cfg << std::endl;
1986 options_description cfg (
"Testnet launcher config options");
1987 options_description
cli (
"launcher command line options");
1992 string bounce_nodes;
1995 bfs::path config_dir;
1996 bfs::path config_file;
2002 (
"timestamp,i",bpo::value<string>(>s),
"set the timestamp for the first block. Use \"now\" to indicate the current time")
2003 (
"launch,l",bpo::value<string>(),
"select a subset of nodes to launch. Currently may be \"all\", \"none\", or \"local\". If not set, the default is to launch all unless an output file is named, in which case it starts none.")
2004 (
"output,o",bpo::value<bfs::path>(&top.
output),
"save a copy of the generated topology in this file")
2005 (
"kill,k", bpo::value<string>(&kill_arg),
"The launcher retrieves the previously started process ids and issues a kill to each.")
2006 (
"down", bpo::value<string>(&down_nodes),
"comma-separated list of node numbers that will be taken down using the sysio-tn_down.sh script")
2007 (
"bounce", bpo::value<string>(&bounce_nodes),
"comma-separated list of node numbers that will be restarted using the sysio-tn_bounce.sh script")
2008 (
"roll", bpo::value<string>(&roll_nodes),
"comma-separated list of host names where the nodes should be rolled to a new version using the sysio-tn_roll.sh script")
2009 (
"version,v",
"print version information")
2010 (
"help,h",
"print this list")
2011 (
"config-dir", bpo::value<bfs::path>(),
"Directory containing configuration files such as config.ini")
2012 (
"config,c", bpo::value<bfs::path>()->default_value(
"config.ini" ),
"Configuration file name relative to config-dir");
2017 bpo::store(bpo::parse_command_line(argc,
argv,
cli), vmap);
2022 if (vmap.count(
"help") > 0) {
2026 if (vmap.count(
"version") > 0) {
2027 cout << sysio::launcher::config::version_str << endl;
2031 if( vmap.count(
"config-dir" ) ) {
2032 config_dir = vmap[
"config-dir"].as<bfs::path>();
2033 if( config_dir.is_relative() )
2034 config_dir = bfs::current_path() / config_dir;
2037 bfs::path config_file_name = config_dir /
"config.ini";
2038 if( vmap.count(
"config" ) ) {
2039 config_file_name = vmap[
"config"].as<bfs::path>();
2040 if( config_file_name.is_relative() )
2041 config_file_name = config_dir / config_file_name;
2044 if(!bfs::exists(config_file_name)) {
2045 if(config_file_name.compare(config_dir /
"config.ini") != 0)
2047 cout <<
"Config file " << config_file_name <<
" missing." << std::endl;
2054 bpo::store(bpo::parse_config_file<char>(config_file_name.make_preferred().string().c_str(),
2059 if (vmap.count(
"launch")) {
2060 string l = vmap[
"launch"].as<
string>();
2061 if (boost::iequals(
l,
"all"))
2063 else if (boost::iequals(
l,
"local"))
2065 else if (boost::iequals(
l,
"remote"))
2067 else if (boost::iequals(
l,
"none"))
2069 else if (boost::iequals(
l,
"verify"))
2080 if (!kill_arg.empty()) {
2081 cout <<
"killing" << std::endl;
2082 if (kill_arg[0] !=
'-') {
2083 kill_arg =
"-" + kill_arg;
2085 top.
kill (mode, kill_arg);
2087 else if (!bounce_nodes.empty()) {
2088 top.
bounce(bounce_nodes);
2090 else if (!down_nodes.empty()) {
2091 top.
down(down_nodes);
2093 else if (!roll_nodes.empty()) {
2094 top.
roll(roll_nodes);
2101 }
catch (bpo::unknown_option &ex) {
2102 cerr << ex.what() << endl;
2112 (ssh_cmd)(scp_cmd)(ssh_identity)(ssh_args) )
2122 (genesis)(ssh_identity)(ssh_args)(
sysio_home)
2123 (host_name)(public_name)
2124 (base_p2p_port)(base_http_port)(def_file_size)
2129 (config_dir_name)(data_dir_name)(p2p_port)
2130 (http_port)(file_size)(
name)(host)
string get_node_num() const
void set_host(host_def *h, bool is_bios)
const string & dot_label()
Used to generate a useful error report when an exception is thrown.
std::string to_detail_string(log_level ll=log_level::all) const
static variant from_file(const fc::path &p, const parse_type ptype=parse_type::legacy_parser, const uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
static bool save_to_file(const T &v, const fc::path &fi, const bool pretty=true, const output_formatting format=output_formatting::stringify_large_ints_and_doubles)
static string to_pretty_string(const variant &v, const yield_function_t &yield, const output_formatting format=output_formatting::stringify_large_ints_and_doubles)
@ stringify_large_ints_and_doubles
An order-preserving dictionary of variants.
static constexpr time_point maximum()
vector< eosd_def > instances
const string & dot_label()
uint16_t http_bios_port()
static string producer_name(unsigned int producer_number, bool shared_producer=false)
vector< private_key_type > keys
vector< string > producers
static const Reg16 bp(Operand::BP)
fc::string to_lower(const fc::string &)
fc::crypto::public_key public_key_type
fc::crypto::private_key private_key_type
const CharType(& source)[N]
const GenericPointer< typename T::ValueType > T2 T::AllocatorType & a
#define T(meth, val, expected)
const string shared_mem_dir
enum_type & operator|=(enum_type &lhs, const enum_type &rhs)
struct local_identity local_id
schedule config_dir_name data_dir_name p2p_port http_port file_size name name keys peers name ipaddr name has_bios producer nonprod db default_sysio_home(ssh)) FC_REFLECT(node_rt_info
schedule config_dir_name data_dir_name p2p_port http_port file_size name name keys peers name ssh_helper(nodes)) FC_REFLECT(server_name_def
schedule config_dir_name data_dir_name p2p_port http_port file_size name name keys peers producers(dont_start)) FC_REFLECT(testnet_def
schedule config_dir_name data_dir_name p2p_port http_port file_size name host(p2p_endpoint)) FC_REFLECT(tn_node_def
void write_default_config(const bfs::path &cfg_file, const options_description &cfg)
schedule config_dir_name data_dir_name p2p_port http_port file_size name name keys peers name ipaddr name has_bios sysio_home(instances)) FC_REFLECT(server_identities
void retrieve_paired_array_parameters(const variables_map &vmap, const std::string &num_selector, const std::string &paired_selector, std::map< uint, T > &selector_map)
producer_name(block_signing_key)) FC_REFLECT(producer_set_def
#define FC_REFLECT(TYPE, MEMBERS)
Specializes fc::reflector for TYPE.
std::vector< string > appenders
std::optional< log_level > level
if not set, then parents level is used.
static logging_config default_config()
vector< node_rt_info > running_nodes
bool do_ssh(const string &cmd, const string &host_name)
void down(const string &node_numbers)
producer_set_def producer_set
pair< host_def, eosd_def > find_node(uint16_t node_num)
bfs::path config_dir_base
void do_command(const host_def &host, const string &name, vector< pair< string, string > > env_pairs, const string &cmd)
void set_options(bpo::options_description &cli)
void write_setprods_file()
string compose_scp_command(const host_def &host, const bfs::path &source, const bfs::path &destination)
string find_and_remove_arg(string str, string substr)
host_def * find_host_by_name_or_address(const string &name)
sysio::chain::genesis_state genesis_from_file
void roll(const string &host_names)
std::optional< uint32_t > max_transaction_cpu_usage
void bounce(const string &node_numbers)
host_def * deploy_config_files(tn_node_def &node)
static string get_node_num(uint16_t node_num)
void write_logging_config_file(tn_node_def &node)
void write_genesis_file(tn_node_def &node)
std::optional< uint32_t > max_block_cpu_usage
vector< pair< host_def, eosd_def > > get_nodes(const string &node_number_list)
void start_all(string >s, launch_modes mode)
std::map< uint, string > specific_nodeop_args
bfs::path server_ident_file
std::map< uint, string > specific_nodeop_installation_paths
void launch(eosd_def &node, string >s)
vector< host_def > bindings
allowed_connection allowed_connections
void make_line(bool make_ring=true)
bool is_bios_ndx(size_t ndx)
bool skip_transaction_signatures
void assign_name(eosd_def &node, bool is_bios)
size_t skip_ndx(size_t from, size_t offset)
host_def * find_host(const string &name)
server_identities servers
void prep_remote_config_dir(eosd_def &node, host_def *host)
void kill(launch_modes mode, string sig_opt)
void format_ssh(const string &cmd, const string &host_name, string &ssh_cmd_line)
void initialize(const variables_map &vmap)
bool next_ndx(size_t &ndx)
bool add_enable_stale_production
void write_config_file(tn_node_def &node)
vector< fc::ip::address > addrs
bool contains(const string &name) const
public_key_type block_signing_key
vector< prodkey_def > schedule
bfs::path local_config_file
vector< server_name_def > producer
string default_sysio_home
vector< server_name_def > nonprod
uint32_t max_transaction_cpu_usage
the maximum billable cpu usage (in microseconds) that the chain will allow regardless of account limi...
uint32_t max_block_cpu_usage
the maxiumum billable cpu usage (in microseconds) for a block
public_key_type initial_key
chain_config_v0 initial_configuration
map< string, tn_node_def > nodes