diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index ffc9cc434..418dbf240 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -63,9 +63,10 @@ impl<'a> MultiConfig<'a> { ) -> Result, ConfiguratorError> { let initial: Box = Box::new(CommandLineVcl::new(vec![String::new()])); - let merged: Box = vcls + let merged = vcls .into_iter() .fold(initial, |so_far, vcl| merge(so_far, vcl)); + let arg_matches = match schema .clone() .get_matches_from_safe(merged.args().into_iter()) @@ -78,6 +79,7 @@ impl<'a> MultiConfig<'a> { _ => return Err(Self::make_configurator_error(e)), }, }; + Ok(MultiConfig { arg_matches }) } @@ -224,6 +226,9 @@ impl NameOnlyVclArg { pub trait VirtualCommandLine { fn vcl_args(&self) -> Vec<&dyn VclArg>; fn args(&self) -> Vec; + fn is_computed(&self) -> bool { + false + } } impl Debug for dyn VirtualCommandLine { @@ -347,10 +352,19 @@ impl EnvironmentVcl { } } +#[derive(Debug)] pub struct ConfigFileVcl { vcl_args: Vec>, } +impl Clone for ConfigFileVcl { + fn clone(&self) -> Self { + ConfigFileVcl { + vcl_args: self.vcl_args.iter().map(|arg| arg.dup()).collect(), + } + } +} + impl VirtualCommandLine for ConfigFileVcl { fn vcl_args(&self) -> Vec<&dyn VclArg> { vcl_args_to_vcl_args(&self.vcl_args) @@ -375,8 +389,8 @@ impl Display for ConfigFileVclError { match self { ConfigFileVclError::OpenError(path, _) => write!( fmt, - "Couldn't open configuration file {:?}. Are you sure it exists?", - path + "Couldn't open configuration file \"{}\". Are you sure it exists?", + path.to_string_lossy() ), ConfigFileVclError::CorruptUtf8(path) => write!( fmt, diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs index 00cee6b7c..fc7e8ced2 100644 --- a/masq_lib/src/shared_schema.rs +++ b/masq_lib/src/shared_schema.rs @@ -235,7 +235,6 @@ pub fn config_file_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("config-file") .long("config-file") .value_name("FILE-PATH") - .default_value("config.toml") .min_values(0) .max_values(1) .required(false) diff --git a/multinode_integration_tests/tests/connection_termination_test.rs b/multinode_integration_tests/tests/connection_termination_test.rs index 14739f794..b557906f7 100644 --- a/multinode_integration_tests/tests/connection_termination_test.rs +++ b/multinode_integration_tests/tests/connection_termination_test.rs @@ -116,27 +116,28 @@ fn actual_server_drop() { let mut server = real_node.make_server(server_port); let masquerader = JsonMasquerader::new(); let (stream_key, return_route_id) = arbitrary_context(); - mock_node - .transmit_package( - mock_node.port_list()[0], - create_request_icp( - &mock_node, - &real_node, - stream_key, - return_route_id, - &server, - cluster.chain, - ), - &masquerader, - real_node.main_public_key(), - real_node.socket_addr(PortSelector::First), - ) - .unwrap(); - server.wait_for_chunk(Duration::from_secs(2)).unwrap(); - server.send_chunk(HTTP_RESPONSE); - mock_node - .wait_for_package(&masquerader, Duration::from_secs(2)) - .unwrap(); + let index: u64 = 0; + request_server_payload( + index, + &cluster, + &real_node, + &mock_node, + &mut server, + &masquerader, + stream_key, + return_route_id, + ); + let index: u64 = 1; + request_server_payload( + index, + &cluster, + &real_node, + &mock_node, + &mut server, + &masquerader, + stream_key, + return_route_id, + ); server.shutdown(); @@ -165,6 +166,40 @@ fn actual_server_drop() { assert!(payload.sequenced_packet.last_data); } +fn request_server_payload( + index: u64, + cluster: &MASQNodeCluster, + real_node: &MASQRealNode, + mock_node: &MASQMockNode, + server: &mut MASQNodeServer, + masquerader: &JsonMasquerader, + stream_key: StreamKey, + return_route_id: u32, +) { + mock_node + .transmit_package( + mock_node.port_list()[0], + create_request_icp( + index, + &mock_node, + &real_node, + stream_key, + return_route_id, + &server, + cluster.chain, + ), + masquerader, + real_node.main_public_key(), + real_node.socket_addr(PortSelector::First), + ) + .unwrap(); + server.wait_for_chunk(Duration::from_secs(2)).unwrap(); + server.send_chunk(HTTP_RESPONSE); + mock_node + .wait_for_package(masquerader, Duration::from_secs(2)) + .unwrap(); +} + #[test] // Given: Exit Node is real_node; originating Node is mock_node. // Given: A stream is established through the exit Node to a server. @@ -178,10 +213,12 @@ fn reported_client_drop() { let mut server = real_node.make_server(server_port); let masquerader = JsonMasquerader::new(); let (stream_key, return_route_id) = arbitrary_context(); + let index: u64 = 0; mock_node .transmit_package( mock_node.port_list()[0], create_request_icp( + index, &mock_node, &real_node, stream_key, @@ -309,6 +346,7 @@ fn arbitrary_context() -> (StreamKey, u32) { } fn create_request_icp( + index: u64, originating_node: &MASQMockNode, exit_node: &MASQRealNode, stream_key: StreamKey, @@ -343,7 +381,7 @@ fn create_request_icp( &node_lib::sub_lib::migrations::client_request_payload::MIGRATIONS, &ClientRequestPayload_0v1 { stream_key, - sequenced_packet: SequencedPacket::new(Vec::from(HTTP_REQUEST), 0, false), + sequenced_packet: SequencedPacket::new(Vec::from(HTTP_REQUEST), index, false), target_hostname: Some(format!("{}", server.local_addr().ip())), target_port: server.local_addr().port(), protocol: ProxyProtocol::HTTP, diff --git a/node/src/accountant/db_access_objects/utils.rs b/node/src/accountant/db_access_objects/utils.rs index c0063acb7..8b78bb5f4 100644 --- a/node/src/accountant/db_access_objects/utils.rs +++ b/node/src/accountant/db_access_objects/utils.rs @@ -17,6 +17,7 @@ use rusqlite::{Row, Statement, ToSql}; use std::fmt::{Debug, Display}; use std::iter::FlatMap; use std::path::{Path, PathBuf}; +use std::string::ToString; use std::time::Duration; use std::time::SystemTime; diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 29562d7f1..880b79448 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -4523,7 +4523,6 @@ mod tests { let factory = Accountant::dao_factory(data_dir); factory.make(); }; - assert_on_initialization_with_panic_on_migration(&data_dir, &act); } } diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index 15a27d4bd..d882ee773 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -18,7 +18,7 @@ use crate::node_configurator::unprivileged_parse_args_configuration::{ UnprivilegedParseArgsConfigurationDaoReal, }; use crate::node_configurator::{ - data_directory_from_context, determine_fundamentals, DirsWrapper, DirsWrapperReal, + data_directory_from_context, determine_user_specific_data, DirsWrapper, DirsWrapperReal, }; use crate::sub_lib::accountant::PaymentThresholds as PaymentThresholdsFromAccountant; use crate::sub_lib::accountant::DEFAULT_SCAN_INTERVALS; @@ -46,10 +46,16 @@ use std::str::FromStr; const CONSOLE_DIAGNOSTICS: bool = false; -const ARG_PAIRS_SENSITIVE_TO_SETUP_ERRS: &[ErrorSensitiveArgPair] = &[ErrorSensitiveArgPair { - blanked_arg: "chain", - linked_arg: "data-directory", -}]; +const ARG_PAIRS_SENSITIVE_TO_SETUP_ERRS: &[ErrorSensitiveArgPair] = &[ + // If we have chain A and data directory X, and then an incoming_setup arrives with chain blanked out and data + // directory Y, we'll preserve the blank chain, but resurrect data directory X. (I'm not sure this is correct; + // perhaps if we're going to take advantage of a default chain, we should also use the default chain's data + // directory. --Dan) + ErrorSensitiveArgPair { + blanked_arg: "chain", + linked_arg: "data-directory", + }, +]; pub type SetupCluster = HashMap; @@ -110,6 +116,10 @@ impl SetupReporter for SetupReporterReal { eprintln_setup("DEFAULTS", &default_setup); eprintln_setup("EXISTING", &existing_setup); eprintln_setup("BLANKED-OUT FORMER VALUES", &blanked_out_former_values); + eprintln_setup( + "PREVENTION TO ERR INDUCED SETUP IMPAIRMENTS", + &prevention_to_err_induced_setup_impairments, + ); eprintln_setup("INCOMING", &incoming_setup); eprintln_setup("ALL BUT CONFIGURED", &all_but_configured); let mut error_so_far = ConfiguratorError::new(vec![]); @@ -487,22 +497,25 @@ impl SetupReporterReal { if let Some(command_line) = command_line_opt.clone() { vcls.push(Box::new(CommandLineVcl::new(command_line))); } - if environment { - vcls.push(Box::new(EnvironmentVcl::new(&app))); - } if config_file { let command_line = match command_line_opt { Some(command_line) => command_line, None => vec![], }; - let (config_file_path, user_specified, _data_directory, _real_user) = - determine_fundamentals(dirs_wrapper, &app, &command_line)?; - let config_file_vcl = match ConfigFileVcl::new(&config_file_path, user_specified) { + let user_specific_data = + determine_user_specific_data(dirs_wrapper, &app, &command_line)?; + let config_file_vcl = match ConfigFileVcl::new( + &user_specific_data.config_file.item, + user_specific_data.config_file.user_specified, + ) { Ok(cfv) => cfv, Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), }; vcls.push(Box::new(config_file_vcl)); } + if environment { + vcls.push(Box::new(EnvironmentVcl::new(&app))); + } make_new_multi_config(&app, vcls) } @@ -1213,6 +1226,7 @@ mod tests { }; use crate::test_utils::{assert_string_contains, rate_pack}; use core::option::Option; + use dirs::home_dir; use masq_lib::blockchains::chains::Chain as Blockchain; use masq_lib::blockchains::chains::Chain::PolyMumbai; use masq_lib::constants::{DEFAULT_CHAIN, DEFAULT_GAS_PRICE}; @@ -1292,7 +1306,7 @@ mod tests { fn everything_in_defaults_is_properly_constructed() { let result = SetupReporterReal::get_default_params(); - assert_eq!(result.is_empty(), false, "{:?}", result); // if we don't have any defaults, let's get rid of all this + assert_eq!(result.is_empty(), true, "{:?}", result); // if we have any defaults, let's get back to false statement here and assert right value line below result.into_iter().for_each(|(name, value)| { assert_eq!(name, value.name); assert_eq!(value.status, Default); @@ -1387,7 +1401,7 @@ mod tests { ), ("chain", DEFAULT_CHAIN.rec().literal_identifier, Default), ("clandestine-port", "1234", Configured), - ("config-file", "config.toml", Default), + ("config-file", "", Blank), ("consuming-private-key", "", Blank), ("crash-point", "", Blank), ( @@ -1566,7 +1580,7 @@ mod tests { ("blockchain-service-url", "https://example2.com", Set), ("chain", TEST_DEFAULT_CHAIN.rec().literal_identifier, Set), ("clandestine-port", "1234", Set), - ("config-file", "config.toml", Default), + ("config-file", "", Blank), ("consuming-private-key", "0011223344556677001122334455667700112233445566770011223344556677", Set), ("crash-point", "Message", Set), ("data-directory", chain_specific_data_dir.to_str().unwrap(), Set), @@ -1639,7 +1653,7 @@ mod tests { ("blockchain-service-url", "https://example3.com", Configured), ("chain", TEST_DEFAULT_CHAIN.rec().literal_identifier, Configured), ("clandestine-port", "1234", Configured), - ("config-file", "config.toml", Default), + ("config-file", "", Blank), ("consuming-private-key", "0011223344556677001122334455667700112233445566770011223344556677", Configured), ("crash-point", "Error", Configured), ("data-directory", home_dir.to_str().unwrap(), Configured), @@ -1694,7 +1708,10 @@ mod tests { .write_all(b"clandestine-port = \"7788\"\n") .unwrap(); config_file.write_all(b"consuming-private-key = \"00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF\"\n").unwrap(); - config_file.write_all(b"crash-point = \"None\"\n").unwrap(); + config_file.write_all(b"crash-point = \"Error\"\n").unwrap(); + config_file + .write_all(b"db-password = \"mainnetPassword\"\n") + .unwrap(); config_file .write_all(b"dns-servers = \"5.6.7.8\"\n") .unwrap(); @@ -1708,7 +1725,7 @@ mod tests { .unwrap(); config_file.write_all(b"min-hops = \"6\"\n").unwrap(); config_file - .write_all(b"neighborhood-mode = \"zero-hop\"\n") + .write_all(b"neighborhood-mode = \"standard\"\n") .unwrap(); config_file.write_all(b"scans = \"off\"\n").unwrap(); config_file.write_all(b"rate-pack = \"2|2|2|2\"\n").unwrap(); @@ -1789,7 +1806,7 @@ mod tests { ), ("chain", TEST_DEFAULT_CHAIN.rec().literal_identifier, Set), ("clandestine-port", "8877", Configured), - ("config-file", "config.toml", Default), + ("config-file", "", Blank), ( "consuming-private-key", "FFEEDDCCBBAA99887766554433221100FFEEDDCCBBAA99887766554433221100", @@ -1948,7 +1965,7 @@ mod tests { ("blockchain-service-url", "", Required), ("chain", TEST_DEFAULT_CHAIN.rec().literal_identifier, Configured), ("clandestine-port", "1234", Configured), - ("config-file", "config.toml", Default), + ("config-file", "", Blank), ("consuming-private-key", "0011223344556677001122334455667700112233445566770011223344556677", Configured), ("crash-point", "Panic", Configured), ("data-directory", home_dir.to_str().unwrap(), Configured), @@ -2028,6 +2045,58 @@ mod tests { assert_eq!(actual_data_directory, expected_data_directory); } + #[test] + fn get_modified_setup_tilde_in_config_file_path() { + let _guard = EnvironmentGuard::new(); + let base_dir = ensure_node_home_directory_exists( + "setup_reporter", + "get_modified_setup_tilde_in_data_directory", + ); + let data_dir = base_dir.join("data_dir"); + std::fs::create_dir_all(home_dir().expect("expect home dir").join("masqhome")).unwrap(); + let mut config_file = File::create( + home_dir() + .expect("expect home dir") + .join("masqhome") + .join("config.toml"), + ) + .unwrap(); + config_file + .write_all(b"blockchain-service-url = \"https://www.mainnet.com\"\n") + .unwrap(); + let existing_setup = setup_cluster_from(vec![ + ("neighborhood-mode", "zero-hop", Set), + ("chain", DEFAULT_CHAIN.rec().literal_identifier, Default), + ( + "data-directory", + &data_dir.to_string_lossy().to_string(), + Default, + ), + ]); + let incoming_setup = vec![ + ("data-directory", "~/masqhome"), + ("config-file", "~/masqhome/config.toml"), + ] + .into_iter() + .map(|(name, value)| UiSetupRequestValue::new(name, value)) + .collect_vec(); + + let expected_config_file_data = "https://www.mainnet.com"; + let dirs_wrapper = Box::new( + DirsWrapperMock::new() + .data_dir_result(Some(data_dir)) + .home_dir_result(Some(base_dir)), + ); + let subject = SetupReporterReal::new(dirs_wrapper); + + let result = subject + .get_modified_setup(existing_setup, incoming_setup) + .unwrap(); + + let actual_config_file_data = result.get("blockchain-service-url").unwrap().value.as_str(); + assert_eq!(actual_config_file_data, expected_config_file_data); + } + #[test] fn get_modified_setup_user_specified_data_directory_depends_on_new_chain_on_success() { let _guard = EnvironmentGuard::new(); @@ -2627,10 +2696,7 @@ mod tests { .calculate_configured_setup(&setup, &data_directory) .0; - assert_eq!( - result.get("config-file").unwrap().value, - "config.toml".to_string() - ); + assert_eq!(result.get("config-file").unwrap().value, "".to_string()); assert_eq!( result.get("gas-price").unwrap().value, GasPrice {} diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index e2baff293..a45a2969a 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -13,15 +13,16 @@ use crate::db_config::persistent_configuration::{ }; use crate::sub_lib::utils::{db_connection_launch_panic, make_new_multi_config}; use clap::{value_t, App}; +use core::option::Option; use dirs::{data_local_dir, home_dir}; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; -use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VclArg}; -use masq_lib::shared_schema::{ - chain_arg, config_file_arg, data_directory_arg, real_user_arg, ConfiguratorError, - DATA_DIRECTORY_HELP, +use masq_lib::multi_config::{ + merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VirtualCommandLine, }; +use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::{add_masq_and_chain_directories, localhost}; +use std::env::current_dir; use std::net::{SocketAddr, TcpListener}; use std::path::{Path, PathBuf}; @@ -29,48 +30,212 @@ pub trait NodeConfigurator { fn configure(&self, multi_config: &MultiConfig) -> Result; } -pub fn determine_fundamentals( +#[derive(Default, Debug)] +pub struct FieldPair { + pub(crate) item: T, + pub(crate) user_specified: bool, +} + +impl FieldPair { + fn new(item: T, user_specified: bool) -> Self { + FieldPair { + item, + user_specified, + } + } +} + +#[derive(Debug)] +pub struct ConfigInitializationData { + pub(crate) chain: FieldPair, + pub(crate) real_user: FieldPair, + pub(crate) data_directory: FieldPair, + pub(crate) config_file: FieldPair, +} + +fn get_chain_from_mc(multi_config: &MultiConfig) -> FieldPair { + let chain = value_m!(multi_config, "chain", String); + match chain { + Some(chain) => FieldPair::new(Chain::from(&*chain), true), + None => FieldPair::new(DEFAULT_CHAIN, false), + } +} + +fn get_real_user_from_mc( + multi_config: &MultiConfig, + dirs_wrapper: &dyn DirsWrapper, +) -> FieldPair { + let real_user = value_m!(multi_config, "real-user", RealUser); + match real_user { + Some(user) => FieldPair::new(user, true), + None => { + #[cfg(target_os = "windows")] + { + FieldPair::new( + RealUser::new(Some(999999), Some(999999), None).populate(dirs_wrapper), + false, + ) + } + #[cfg(not(target_os = "windows"))] + { + FieldPair::new( + RealUser::new(None, None, None).populate(dirs_wrapper), + false, + ) + } + } + } +} + +fn get_data_directory_from_mc( + multi_config: &MultiConfig, + dirs_wrapper: &dyn DirsWrapper, + real_user: &RealUser, + chain: &Chain, +) -> FieldPair { + let data_directory = value_m!(multi_config, "data-directory", PathBuf); + match data_directory { + Some(data_dir) => match data_dir.starts_with("~/") { + true => { + let home_dir_from_wrapper = dirs_wrapper + .home_dir() + .expect("expected users home dir") + .to_str() + .expect("expected home dir") + .to_string(); + let replaced_tilde_dir = + data_dir + .display() + .to_string() + .replacen('~', home_dir_from_wrapper.as_str(), 1); + FieldPair::new(PathBuf::from(replaced_tilde_dir), true) + } + false => FieldPair::new(data_dir, true), + }, + None => FieldPair::new( + data_directory_from_context(dirs_wrapper, real_user, *chain), + false, + ), + } +} + +fn replace_tilde(config_path: PathBuf) -> PathBuf { + match config_path.starts_with("~") { + true => PathBuf::from( + config_path.display().to_string().replacen( + '~', + home_dir() + .expect("expected users home_dir") + .to_str() + .expect("expected str home_dir"), + 1, + ), + ), + false => config_path, + } +} + +fn replace_dots(config_path: PathBuf) -> PathBuf { + match config_path.starts_with("./") || config_path.starts_with("../") { + true => current_dir() + .expect("expected current dir") + .join(config_path), + false => config_path, + } +} + +fn replace_relative_path( + config_path: PathBuf, + data_directory_def: bool, + data_directory: &Path, + panic: &mut bool, +) -> PathBuf { + match config_path.is_relative() { + true => match data_directory_def { + true => data_directory.join(config_path), + false => { + *panic = true; + config_path + } + }, + false => config_path, + } +} + +fn get_config_file_from_mc( + multi_config: &MultiConfig, + data_directory: &Path, + data_directory_def: bool, +) -> FieldPair { + let mut panic: bool = false; + let config_file = value_m!(multi_config, "config-file", PathBuf); + match config_file { + Some(config_path) => { + let config_path = replace_tilde(config_path); + let config_path = replace_dots(config_path); + let config_path = + replace_relative_path(config_path, data_directory_def, data_directory, &mut panic); + if panic { + panic!( + "If the config file is given with a naked relative path ({}), the data directory must be given to serve as the root for the config-file path.", + config_path.to_string_lossy() + ); + } + FieldPair::new(config_path, true) + } + None => { + let path = data_directory.join(PathBuf::from("config.toml")); + match path.is_file() { + true => FieldPair::new(path, true), + false => FieldPair::new(path, false), + } + } + } +} + +fn config_file_data_dir_real_user_chain_from_mc( + dirs_wrapper: &dyn DirsWrapper, + multi_config: MultiConfig, +) -> ConfigInitializationData { + let mut initialization_data = ConfigInitializationData { + chain: Default::default(), + real_user: Default::default(), + data_directory: Default::default(), + config_file: Default::default(), + }; + + initialization_data.chain = get_chain_from_mc(&multi_config); + initialization_data.real_user = get_real_user_from_mc(&multi_config, dirs_wrapper); + initialization_data.data_directory = get_data_directory_from_mc( + &multi_config, + dirs_wrapper, + &initialization_data.real_user.item, + &initialization_data.chain.item, + ); + initialization_data.config_file = get_config_file_from_mc( + &multi_config, + &initialization_data.data_directory.item, + initialization_data.data_directory.user_specified, + ); + initialization_data +} + +pub fn determine_user_specific_data( dirs_wrapper: &dyn DirsWrapper, app: &App, args: &[String], -) -> Result<(PathBuf, bool, PathBuf, RealUser), ConfiguratorError> { - let orientation_schema = App::new("MASQNode") - .arg(chain_arg()) - .arg(real_user_arg()) - .arg(data_directory_arg(DATA_DIRECTORY_HELP)) - .arg(config_file_arg()); - let orientation_args: Vec> = merge( +) -> Result { + let orientation_args: Box = merge( Box::new(EnvironmentVcl::new(app)), Box::new(CommandLineVcl::new(args.to_vec())), - ) - .vcl_args() - .into_iter() - .filter(|vcl_arg| { - (vcl_arg.name() == "--chain") - || (vcl_arg.name() == "--real-user") - || (vcl_arg.name() == "--data-directory") - || (vcl_arg.name() == "--config-file") - }) - .map(|vcl_arg| vcl_arg.dup()) - .collect(); - let orientation_vcl = CommandLineVcl::from(orientation_args); - let multi_config = make_new_multi_config(&orientation_schema, vec![Box::new(orientation_vcl)])?; - let config_file_path = value_m!(multi_config, "config-file", PathBuf) - .expect("config-file parameter is not properly defaulted by clap"); - let user_specified = multi_config.occurrences_of("config-file") > 0; - let (real_user, data_directory_path, chain) = - real_user_data_directory_path_and_chain(dirs_wrapper, &multi_config); - let data_directory = match data_directory_path { - Some(data_dir) => data_dir, - None => data_directory_from_context(dirs_wrapper, &real_user, chain), - }; - let config_file_path = if config_file_path.is_relative() { - data_directory.join(config_file_path) - } else { - config_file_path - }; - - Ok((config_file_path, user_specified, data_directory, real_user)) + ); + /* We create multiconfig to retrieve chain, real-user, data-directory and config file, to establish ConfigVcl */ + let first_multi_config = + make_new_multi_config(app, vec![orientation_args]).expect("expected MultiConfig"); + let initialization_data = + config_file_data_dir_real_user_chain_from_mc(dirs_wrapper, first_multi_config); + + Ok(initialization_data) } pub fn initialize_database( @@ -159,6 +324,7 @@ mod tests { use super::*; use crate::node_test_utils::DirsWrapperMock; use crate::test_utils::ArgsBuilder; + use masq_lib::shared_schema::{config_file_arg, data_directory_arg, DATA_DIRECTORY_HELP}; use masq_lib::test_utils::environment_guard::EnvironmentGuard; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use masq_lib::utils::find_free_port; @@ -203,26 +369,37 @@ mod tests { ); let _guard = EnvironmentGuard::new(); let args = ArgsBuilder::new() - .param("--clandestine-port", "2345") .param( "--data-directory", &data_directory.to_string_lossy().to_string(), ) .param("--config-file", "booga.toml"); let args_vec: Vec = args.into(); + let app = determine_config_file_path_app(); + let user_specific_data = + determine_user_specific_data(&DirsWrapperReal {}, &app, args_vec.as_slice()).unwrap(); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( - &DirsWrapperReal {}, - &determine_config_file_path_app(), - args_vec.as_slice(), - ) - .unwrap(); assert_eq!( - &format!("{}", config_file_path.parent().unwrap().display()), - &data_directory.to_string_lossy().to_string(), + &format!( + "{}", + user_specific_data + .config_file + .item + .parent() + .unwrap() + .display() + ), + &user_specific_data + .data_directory + .item + .to_string_lossy() + .to_string(), + ); + assert_eq!( + "booga.toml", + user_specific_data.config_file.item.file_name().unwrap() ); - assert_eq!("booga.toml", config_file_path.file_name().unwrap()); - assert_eq!(true, user_specified); + assert_eq!(user_specific_data.real_user.user_specified, false); } #[test] @@ -239,19 +416,31 @@ mod tests { &data_directory.to_string_lossy().to_string(), ); std::env::set_var("MASQ_CONFIG_FILE", "booga.toml"); - - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( - &DirsWrapperReal {}, - &determine_config_file_path_app(), - args_vec.as_slice(), - ) - .unwrap(); + let app = determine_config_file_path_app(); + let user_specific_data = + determine_user_specific_data(&DirsWrapperReal {}, &app, args_vec.as_slice()).unwrap(); + assert_eq!( + format!( + "{}", + user_specific_data + .config_file + .item + .parent() + .unwrap() + .display() + ), + user_specific_data + .data_directory + .item + .to_string_lossy() + .to_string(), + ); assert_eq!( - format!("{}", config_file_path.parent().unwrap().display()), - data_directory.to_string_lossy().to_string(), + "booga.toml", + user_specific_data.config_file.item.file_name().unwrap() ); - assert_eq!("booga.toml", config_file_path.file_name().unwrap()); - assert_eq!(true, user_specified); + assert_eq!(user_specific_data.config_file.user_specified, true); + assert_eq!(user_specific_data.real_user.user_specified, false); //all these assertions of 'real_user_specified' was incorrect, no idea how this tests could pass before } #[cfg(not(target_os = "windows"))] @@ -263,18 +452,16 @@ mod tests { .param("--config-file", "/tmp/booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( - &DirsWrapperReal {}, - &determine_config_file_path_app(), - args_vec.as_slice(), - ) - .unwrap(); + let app = determine_config_file_path_app(); + let user_specific_data = + determine_user_specific_data(&DirsWrapperReal {}, &app, args_vec.as_slice()).unwrap(); assert_eq!( "/tmp/booga.toml", - &format!("{}", config_file_path.display()) + &format!("{}", user_specific_data.config_file.item.display()) ); - assert_eq!(true, user_specified); + assert_eq!(user_specific_data.config_file.user_specified, true); + assert_eq!(user_specific_data.real_user.user_specified, false); } #[cfg(target_os = "windows")] @@ -286,7 +473,7 @@ mod tests { .param("--config-file", r"\tmp\booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( + let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -295,9 +482,10 @@ mod tests { assert_eq!( r"\tmp\booga.toml", - &format!("{}", config_file_path.display()) + &format!("{}", user_specific_data.config_file.item.display()) ); - assert_eq!(true, user_specified); + assert_eq!(user_specific_data.config_file.user_specified, true); + assert_eq!(user_specific_data.real_user.user_specified, false); } #[cfg(target_os = "windows")] @@ -309,7 +497,7 @@ mod tests { .param("--config-file", r"c:\tmp\booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( + let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -318,9 +506,9 @@ mod tests { assert_eq!( r"c:\tmp\booga.toml", - &format!("{}", config_file_path.display()) + &format!("{}", user_specific_data.config_file.item.display()) ); - assert_eq!(true, user_specified); + assert_eq!(user_specific_data.real_user.user_specified, false); } #[cfg(target_os = "windows")] @@ -332,7 +520,7 @@ mod tests { .param("--config-file", r"\\TMP\booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( + let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -341,9 +529,9 @@ mod tests { assert_eq!( r"\\TMP\booga.toml", - &format!("{}", config_file_path.display()) + &format!("{}", user_specific_data.config_file.item.display()) ); - assert_eq!(true, user_specified); + assert_eq!(user_specific_data.real_user.user_specified, false); } #[cfg(target_os = "windows")] @@ -356,7 +544,7 @@ mod tests { .param("--config-file", r"c:tmp\booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( + let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -365,9 +553,9 @@ mod tests { assert_eq!( r"c:tmp\booga.toml", - &format!("{}", config_file_path.display()) + &format!("{}", user_specific_data.config_file.item.display()) ); - assert_eq!(true, user_specified); + assert_eq!(user_specific_data.real_user.user_specified, false); } #[test] diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 10150b6e1..bb930a9d1 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -1,16 +1,15 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::bootstrapper::BootstrapperConfig; -use crate::node_configurator::DirsWrapperReal; -use crate::node_configurator::{initialize_database, DirsWrapper, NodeConfigurator}; +use crate::node_configurator::{initialize_database, DirsWrapper, FieldPair, NodeConfigurator}; +use crate::node_configurator::{ConfigInitializationData, DirsWrapperReal}; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; -use masq_lib::multi_config::MultiConfig; +use masq_lib::multi_config::{MultiConfig, VirtualCommandLine}; use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::NeighborhoodModeLight; use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr}; -use std::path::PathBuf; use clap::value_t; use log::LevelFilter; @@ -24,9 +23,9 @@ use crate::node_configurator::unprivileged_parse_args_configuration::{ UnprivilegedParseArgsConfiguration, UnprivilegedParseArgsConfigurationDaoReal, }; use crate::node_configurator::{ - data_directory_from_context, determine_fundamentals, real_user_data_directory_path_and_chain, + data_directory_from_context, determine_user_specific_data, + real_user_data_directory_path_and_chain, }; -use crate::server_initializer::GatheredParams; use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::cryptde_null::CryptDENull; use crate::sub_lib::utils::make_new_multi_config; @@ -136,35 +135,107 @@ fn collect_externals_from_multi_config( ) } +fn extract_values_vcl_fill_multiconfig_vec( + full_multi_config: MultiConfig, + initialization_data: ConfigInitializationData, +) -> Vec { + let config_file_path = initialization_data.config_file.item; + let check_value_from_mc = + |multi_config_value: Option, + initialization_data_val: &str, + initialization_data_spec: bool| match multi_config_value { + Some(arg) => FieldPair { + item: arg, + user_specified: true, + }, + None => FieldPair { + item: initialization_data_val.to_string(), + user_specified: initialization_data_spec, + }, + }; + let cf_real_user = check_value_from_mc( + value_m!(full_multi_config, "real-user", String), + initialization_data.real_user.item.to_string().as_str(), + initialization_data.real_user.user_specified, + ); + let mut specified_vec: Vec = vec!["".to_string()]; + let fill_the_box = + |key: &str, value: &str, vec: &mut Vec| match vec.contains(&key.to_string()) { + true => { + let index = vec + .iter() + .position(|r| r == key) + .expect("expected index of vcl name") + + 1; + vec[index] = value.to_string(); + } + false => { + vec.push(key.to_string()); + vec.push(value.to_string()); + } + }; + fill_the_box( + "--config-file", + config_file_path.as_path().to_string_lossy().as_ref(), + &mut specified_vec, + ); + fill_the_box( + "--data-directory", + initialization_data + .data_directory + .item + .to_string_lossy() + .as_ref(), + &mut specified_vec, + ); + fill_the_box( + "--real-user", + cf_real_user.item.as_str(), + &mut specified_vec, + ); + + specified_vec +} + pub fn server_initializer_collected_params<'a>( dirs_wrapper: &dyn DirsWrapper, args: &[String], -) -> Result, ConfiguratorError> { +) -> Result, ConfiguratorError> { let app = app_node(); - - let (config_file_path, user_specified, data_directory, real_user) = - determine_fundamentals(dirs_wrapper, &app, args)?; - - let config_file_vcl = match ConfigFileVcl::new(&config_file_path, user_specified) { - Ok(cfv) => Box::new(cfv), + let initialization_data = determine_user_specific_data(dirs_wrapper, &app, args)?; + let config_file_vcl = match ConfigFileVcl::new( + &initialization_data.config_file.item, + initialization_data.config_file.user_specified, + ) { + Ok(cfv) => cfv, Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), }; - let full_multi_config = make_new_multi_config( + + let environment_vcl = EnvironmentVcl::new(&app); + let commandline_vcl = CommandLineVcl::new(args.to_vec()); + let multiconfig_for_values_extraction = make_new_multi_config( &app, vec![ - Box::new(CommandLineVcl::new(args.to_vec())), + Box::new(config_file_vcl.clone()), Box::new(EnvironmentVcl::new(&app)), - config_file_vcl, + Box::new(CommandLineVcl::new(commandline_vcl.args())), ], - )?; - let config_file_path = - value_m!(full_multi_config, "config-file", PathBuf).expect("defaulted param"); - Ok(GatheredParams::new( - full_multi_config, - config_file_path, - real_user, - data_directory, - )) + ) + .expect("expexted MultiConfig"); + let specified_vec = extract_values_vcl_fill_multiconfig_vec( + multiconfig_for_values_extraction, + initialization_data, + ); + let mut multi_config_args_vec: Vec> = vec![ + Box::new(config_file_vcl), + Box::new(environment_vcl), + Box::new(commandline_vcl), + ]; + multi_config_args_vec.push(Box::new(CommandLineVcl::new(specified_vec))); + + let full_multi_config = make_new_multi_config(&app, multi_config_args_vec)?; + + Ok(full_multi_config) } pub fn establish_port_configurations(config: &mut BootstrapperConfig) { @@ -305,15 +376,18 @@ mod tests { make_pre_populated_mocked_directory_wrapper, make_simplified_multi_config, }; use crate::test_utils::{assert_string_contains, main_cryptde, ArgsBuilder}; + use dirs::home_dir; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::multi_config::VirtualCommandLine; + use masq_lib::shared_schema::ParamError; use masq_lib::test_utils::environment_guard::{ClapGuard, EnvironmentGuard}; use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; - use masq_lib::utils::{running_test, slice_of_strs_to_vec_of_strings}; + use masq_lib::utils::running_test; use rustc_hex::FromHex; use std::convert::TryFrom; - use std::fs::File; + use std::env::current_dir; + use std::fs::{canonicalize, create_dir_all, File}; use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; @@ -459,8 +533,9 @@ mod tests { #[test] fn server_initializer_collected_params_can_read_parameters_from_config_file() { - running_test(); let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + running_test(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard", "server_initializer_collected_params_can_read_parameters_from_config_file", @@ -472,14 +547,14 @@ mod tests { .unwrap(); } let directory_wrapper = make_pre_populated_mocked_directory_wrapper(); - - let gathered_params = server_initializer_collected_params( - &directory_wrapper, - &slice_of_strs_to_vec_of_strings(&["", "--data-directory", home_dir.to_str().unwrap()]), - ) - .unwrap(); - - let multi_config = gathered_params.multi_config; + let args = ArgsBuilder::new().param("--data-directory", home_dir.to_str().unwrap()); + let args_vec: Vec = args.into(); + let multi_config = + server_initializer_collected_params(&directory_wrapper, args_vec.as_slice()).unwrap(); + assert_eq!( + value_m!(multi_config, "data-directory", String).unwrap(), + home_dir.to_str().unwrap() + ); assert_eq!( value_m!(multi_config, "dns-servers", String).unwrap(), "111.111.111.111,222.222.222.222".to_string() @@ -516,8 +591,8 @@ mod tests { let multi_config = make_new_multi_config( &app_node(), vec![ - Box::new(CommandLineVcl::new(args.into())), Box::new(ConfigFileVcl::new(&config_file_path, false).unwrap()), + Box::new(CommandLineVcl::new(args.into())), ], ) .unwrap(); @@ -638,10 +713,7 @@ mod tests { privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); - assert_eq!( - Some(PathBuf::from("config.toml")), - value_m!(multi_config, "config-file", PathBuf) - ); + assert_eq!(None, value_m!(multi_config, "config-file", PathBuf)); assert_eq!( config.dns_servers, vec!(SocketAddr::from_str("1.1.1.1:53").unwrap()) @@ -695,10 +767,7 @@ mod tests { privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); - assert_eq!( - Some(PathBuf::from("config.toml")), - value_m!(multi_config, "config-file", PathBuf) - ); + assert_eq!(None, value_m!(multi_config, "config-file", PathBuf)); assert_eq!( config.dns_servers, vec!(SocketAddr::from_str("1.1.1.1:53").unwrap()) @@ -738,13 +807,478 @@ mod tests { assert_eq!(config.crash_point, CrashPoint::Panic); } + fn fill_up_config_file(mut config_file: File) { + { + config_file + .write_all(b"blockchain-service-url = \"https://www.mainnet2.com\"\n") + .unwrap(); + config_file + .write_all(b"clandestine-port = \"7788\"\n") + .unwrap(); + config_file.write_all(b"consuming-private-key = \"00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF\"\n").unwrap(); + config_file.write_all(b"crash-point = \"None\"\n").unwrap(); + config_file + .write_all(b"dns-servers = \"5.6.7.8\"\n") + .unwrap(); + config_file + .write_all(b"earning-wallet = \"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n") + .unwrap(); + config_file.write_all(b"gas-price = \"77\"\n").unwrap(); + config_file.write_all(b"ip = \"6.6.6.6\"\n").unwrap(); + config_file.write_all(b"log-level = \"trace\"\n").unwrap(); + config_file + .write_all(b"mapping-protocol = \"pcp\"\n") + .unwrap(); + config_file.write_all(b"min-hops = \"6\"\n").unwrap(); + config_file + .write_all(b"neighborhood-mode = \"zero-hop\"\n") + .unwrap(); + config_file + .write_all(b"payment-thresholds = \"3333|55|33|646|999|999\"\n") + .unwrap(); + config_file.write_all(b"rate-pack = \"2|2|2|2\"\n").unwrap(); + config_file + .write_all(b"real-user = \"1002:1002:/home/wooga\"\n") + .unwrap(); + config_file + .write_all(b"scan-intervals = \"111|100|99\"\n") + .unwrap(); + config_file.write_all(b"scans = \"off\"\n").unwrap(); + } + } + + #[test] + fn server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf() + { + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + running_test(); + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf", + ); + let data_dir = &home_dir.join("data_dir"); + let config_file_relative = File::create(PathBuf::from("./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf").join("config.toml")).unwrap(); + fill_up_config_file(config_file_relative); + let env_vec_array = vec![ + ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf/config.toml"), + ]; + env_vec_array + .clone() + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + let args = ArgsBuilder::new(); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let env_multiconfig = result.unwrap(); + + assert_eq!( + value_m!(env_multiconfig, "dns-servers", String).unwrap(), + "5.6.7.8".to_string() + ); + #[cfg(not(target_os = "windows"))] + { + assert_eq!( + value_m!(env_multiconfig, "real-user", String).unwrap(), + "1002:1002:/home/wooga".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf/config.toml")).to_string_lossy().to_string() + ); + } + #[cfg(target_os = "windows")] + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf/home\\data_dir\\MASQ\\polygon-mainnet".to_string() + ); + } + + #[test] + fn server_initializer_collected_params_handles_only_path_in_config_file_param() { + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + running_test(); + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "server_initializer_collected_params_handles_only_path_in_config_file_param", + ); + let home_dir = canonicalize(home_dir).unwrap(); + let data_dir = &home_dir.join("data_dir"); + + let args = ArgsBuilder::new() + .param( + "--data-directory", + home_dir.clone().display().to_string().as_str(), + ) + .param( + "--config-file", + home_dir.clone().display().to_string().as_str(), + ); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let result = + server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()).unwrap_err(); + + #[cfg(target_os = "windows")] + let result_path = format!( + "Couldn't open configuration file \"{}\". Are you sure it exists?", + current_dir() + .expect("expected current dir") + .as_path() + .join(home_dir.as_path()) + .to_str() + .unwrap() + ); + #[cfg(not(target_os = "windows"))] + let result_path = format!( + "The permissions on configuration file \"{}\" make it unreadable.", + current_dir() + .expect("expected current dir") + .as_path() + .join(home_dir.as_path()) + .to_str() + .unwrap() + ); + let expected = + ConfiguratorError::new(vec![ParamError::new("config-file", result_path.as_str())]); + + assert_eq!(result, expected); + } + + #[test] + fn server_initializer_collected_params_rewrite_config_files_parameters_from_command_line() { + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + running_test(); + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "server_initializer_collected_params_rewrite_config_files_parameters_from_command_line", + ); + let home_dir = canonicalize(home_dir).unwrap(); + let data_dir = &home_dir.join("data_dir"); + let config_file_relative = File::create(home_dir.join("config.toml")).unwrap(); + fill_up_config_file(config_file_relative); + let env_vec_array = vec![("MASQ_CONFIG_FILE", home_dir.join("config.toml"))]; + env_vec_array + .clone() + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + let args = ArgsBuilder::new() + .param("--blockchain-service-url", "https://www.mainnet0.com") + .param("--real-user", "9999:9999:/home/booga") + .param("--ip", "8.5.7.6") + .param("--neighborhood-mode", "standard") + .param("--clandestine-port", "2345"); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let env_multiconfig = result.unwrap(); + + #[cfg(not(target_os = "windows"))] + assert_eq!( + value_m!(env_multiconfig, "real-user", String).unwrap(), + "9999:9999:/home/booga".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + home_dir.join("config.toml").display().to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "blockchain-service-url", String).unwrap(), + "https://www.mainnet0.com".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "ip", String).unwrap(), + "8.5.7.6".to_string() + ); + #[cfg(target_os = "windows")] + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "/home/booga\\data_dir\\MASQ\\polygon-mainnet".to_string() + ); + } + + #[test] + fn server_initializer_collected_params_rewrite_config_files_parameters_from_environment() { + running_test(); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "server_initializer_collected_params_rewrite_config_files_parameters_from_environment", + ); + let home_dir = canonicalize(home_dir).unwrap(); + let data_dir = &home_dir.join("data_dir"); + let config_file_relative = File::create(home_dir.join("config.toml")).unwrap(); + fill_up_config_file(config_file_relative); + let env_vec_array = vec![ + ( + "MASQ_CONFIG_FILE", + home_dir.clone().join("config.toml").display().to_string(), + ), + ( + "MASQ_BLOCKCHAIN_SERVICE_URL", + "https://www.mainnet0.com".to_string(), + ), + ("MASQ_REAL_USER", "9999:9999:/home/booga".to_string()), + ("MASQ_IP", "8.5.7.6".to_string()), + ]; + env_vec_array + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + let args = ArgsBuilder::new(); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let env_multiconfig = result.unwrap(); + + #[cfg(not(target_os = "windows"))] + assert_eq!( + value_m!(env_multiconfig, "real-user", String).unwrap(), + "9999:9999:/home/booga".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + home_dir.join("config.toml").display().to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "blockchain-service-url", String).unwrap(), + "https://www.mainnet0.com".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "ip", String).unwrap(), + "8.5.7.6".to_string() + ); + #[cfg(target_os = "windows")] + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "/home/booga\\data_dir\\MASQ\\polygon-mainnet".to_string() + ); + } + + #[test] + fn server_initializer_collected_params_handle_tilde_in_path_config_file_from_commandline_and_real_user_from_config_file( + ) { + running_test(); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = home_dir().expect("expectexd home dir"); + let data_dir = &home_dir.join("masqhome"); + let _create_data_dir = create_dir_all(data_dir); + let config_file_relative = File::create(data_dir.join("config.toml")).unwrap(); + fill_up_config_file(config_file_relative); + let env_vec_array = vec![ + ("MASQ_BLOCKCHAIN_SERVICE_URL", "https://www.mainnet2.com"), + #[cfg(not(target_os = "windows"))] + ("MASQ_REAL_USER", "9999:9999:booga"), + ]; + env_vec_array + .clone() + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + #[cfg(not(target_os = "windows"))] + let args = ArgsBuilder::new() + .param("--blockchain-service-url", "https://www.mainnet1.com") + .param("--config-file", "~/masqhome/config.toml") + .param("--data-directory", "~/masqhome"); + #[cfg(target_os = "windows")] + let args = ArgsBuilder::new() + .param("--blockchain-service-url", "https://www.mainnet1.com") + .param("--config-file", "~\\masqhome\\config.toml") + .param("--data-directory", "~\\masqhome"); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.to_path_buf())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let multiconfig = result.unwrap(); + + assert_eq!( + value_m!(multiconfig, "data-directory", String).unwrap(), + data_dir.to_string_lossy().to_string() + ); + #[cfg(not(target_os = "windows"))] + { + assert_eq!( + value_m!(multiconfig, "real-user", String).unwrap(), + "9999:9999:booga" + ); + } + assert_eq!( + value_m!(multiconfig, "config-file", String).unwrap(), + data_dir + .join(PathBuf::from("config.toml")) + .to_string_lossy() + .to_string() + ); + assert_eq!( + value_m!(multiconfig, "blockchain-service-url", String).unwrap(), + "https://www.mainnet1.com" + ); + // finally we assert some value from config-file to proof we are reading it + assert_eq!(value_m!(multiconfig, "ip", String).unwrap(), "6.6.6.6"); + } + + #[test] + fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_directory( + ) { + running_test(); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_directory"); + let data_dir = &home_dir.join("data_dir"); + create_dir_all(home_dir.join("config")).expect("expected directory for config"); + let config_file_relative = File::create(&home_dir.join("config/config.toml")).unwrap(); + fill_up_config_file(config_file_relative); + vec![ + ("MASQ_CONFIG_FILE", "config/config.toml"), + ("MASQ_DATA_DIRECTORY", "/unexistent/directory"), + #[cfg(not(target_os = "windows"))] + ("MASQ_REAL_USER", "999:999:/home/malooga"), + ] + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + let args = ArgsBuilder::new() + .param("--real-user", "1001:1001:cooga") + .param("--data-directory", &home_dir.to_string_lossy().to_string()); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let multiconfig = result.unwrap(); + + assert_eq!( + &value_m!(multiconfig, "data-directory", String).unwrap(), + &home_dir.to_string_lossy().to_string() + ); + assert_eq!(value_m!(multiconfig, "ip", String).unwrap(), "6.6.6.6"); + #[cfg(not(target_os = "windows"))] + assert_eq!( + &value_m!(multiconfig, "real-user", String).unwrap(), + "1001:1001:cooga" + ); + } + + #[test] + #[should_panic( + expected = "If the config file is given with a naked relative path (config/config.toml), the data directory must be given to serve as the root for the config-file path." + )] + fn server_initializer_collected_params_fails_on_naked_dir_config_file_without_data_directory() { + running_test(); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_fails_on_naked_dir_config_file_without_data_directory"); + + let data_dir = &home_dir.join("data_dir"); + vec![("MASQ_CONFIG_FILE", "config/config.toml")] + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + let args = ArgsBuilder::new(); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let _result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + } + + #[test] + fn server_initializer_collected_params_combine_vcls_properly() { + running_test(); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "server_initializer_collected_params_combine_vcls_properly", + ); + let data_dir = &home_dir.join("data_dir"); + let config_file = File::create(&home_dir.join("booga.toml")).unwrap(); + let current_directory = current_dir().unwrap(); + fill_up_config_file(config_file); + + let env_vec_array = vec![ + ("MASQ_CONFIG_FILE", "booga.toml"), + ("MASQ_CLANDESTINE_PORT", "8888"), + ("MASQ_DNS_SERVERS", "1.2.3.4"), + ("MASQ_DATA_DIRECTORY", "/nonexistent/directory/home"), + #[cfg(not(target_os = "windows"))] + ("MASQ_REAL_USER", "9999:9999:booga"), + ]; + env_vec_array + .clone() + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + let args = ArgsBuilder::new() + .param("--data-directory", current_directory.join(Path::new("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vcls_properly/home")).to_string_lossy().to_string().as_str()) + .param("--clandestine-port", "1111") + .param("--real-user", "1001:1001:cooga"); + let args_vec: Vec = args.into(); + + let params = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let multiconfig = params.as_ref().unwrap(); + + assert_eq!( + value_m!(multiconfig, "clandestine-port", String).unwrap(), + "1111".to_string() + ); + assert_eq!( + value_m!(multiconfig, "dns-servers", String).unwrap(), + "1.2.3.4".to_string() + ); + assert_eq!( + value_m!(multiconfig, "ip", String).unwrap(), + "6.6.6.6".to_string() + ); + #[cfg(not(target_os = "windows"))] + { + assert_eq!( + value_m!(multiconfig, "config-file", String).unwrap(), + current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vcls_properly/home/booga.toml").to_string_lossy().to_string() + ); + assert_eq!( + value_m!(multiconfig, "real-user", String).unwrap(), + "1001:1001:cooga".to_string() + ); + } + #[cfg(target_os = "windows")] + assert_eq!( + value_m!(multiconfig, "config-file", String).unwrap(), + current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vcls_properly/home\\booga.toml").to_string_lossy().to_string() + ); + } + #[test] fn server_initializer_collected_params_senses_when_user_specifies_config_file() { running_test(); let home_dir = PathBuf::from("/unexisting_home/unexisting_alice"); let data_dir = home_dir.join("data_dir"); + #[cfg(not(target_os = "windows"))] let args = ArgsBuilder::new() - .param("--config-file", "booga.toml") // nonexistent config file: should return error because user-specified + .param("--config-file", "/home/booga/booga.toml") // nonexistent config file: should return error because user-specified + .param("--chain", "polygon-mainnet"); + #[cfg(target_os = "windows")] + let args = ArgsBuilder::new() + .param("--config-file", "C:\\home\\booga\\booga.toml") // nonexistent config file: should return error because user-specified .param("--chain", "polygon-mainnet"); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() @@ -877,9 +1411,13 @@ mod tests { assert_eq!(config.blockchain_bridge_config.gas_price, 1); } + #[should_panic( + expected = "expected MultiConfig: ConfiguratorError { param_errors: [ParamError { parameter: \"gas-price\", reason: \"Invalid value: unleaded\" }] }" + )] #[test] fn server_initializer_collected_params_rejects_invalid_gas_price() { running_test(); + let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); let args = ArgsBuilder::new().param("--gas-price", "unleaded"); let args_vec: Vec = args.into(); @@ -1056,20 +1594,21 @@ mod tests { .data_dir_result(Some(PathBuf::from(standard_data_dir))), }; - let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()) - .unwrap() - .data_directory - .to_string_lossy() - .to_string(); + let result = + server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()).unwrap(); - assert_eq!(result, expected.unwrap()); + assert_eq!( + value_m!(result, "data-directory", String).unwrap(), + expected.unwrap() + ); } #[test] fn server_initializer_collected_params_senses_when_user_specifies_data_directory_without_chain_specific_directory( ) { - running_test(); let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + running_test(); let home_dir = Path::new("/home/cooga"); let home_dir_poly_main = home_dir.join(".local").join("MASQ").join("polygon-mainnet"); let home_dir_poly_mumbai = home_dir.join(".local").join("MASQ").join("polygon-mumbai"); diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index 9bc07212f..048e8d6fa 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -8,6 +8,7 @@ use crate::node_configurator::{DirsWrapper, DirsWrapperReal}; use crate::run_modes_factories::{RunModeResult, ServerInitializer}; use crate::sub_lib::socket_server::ConfiguredByPrivilege; use backtrace::Backtrace; +use clap::value_t; use flexi_logger::{ Cleanup, Criterion, DeferredNow, Duplicate, LevelFilter, LogSpecBuilder, Logger, Naming, Record, }; @@ -17,7 +18,6 @@ use log::{log, Level}; use masq_lib::command::StdStreams; use masq_lib::logger; use masq_lib::logger::{real_format_function, POINTER_TO_FORMAT_FUNCTION}; -use masq_lib::multi_config::MultiConfig; use masq_lib::shared_schema::ConfiguratorError; use std::any::Any; use std::io; @@ -36,35 +36,39 @@ pub struct ServerInitializerReal { impl ServerInitializer for ServerInitializerReal { fn go(&mut self, streams: &mut StdStreams<'_>, args: &[String]) -> RunModeResult { - let params = server_initializer_collected_params(self.dirs_wrapper.as_ref(), args)?; + let multi_config = server_initializer_collected_params(self.dirs_wrapper.as_ref(), args)?; + let real_user = value_m!(multi_config, "real-user", RealUser) + .expect("ServerInitializer: Real user not present in Multi Config"); + let data_directory = value_m!(multi_config, "data-directory", String) + .expect("ServerInitializer: Data directory not present in Multi Config"); let result: RunModeResult = Ok(()) .combine_results( self.dns_socket_server .as_mut() - .initialize_as_privileged(¶ms.multi_config), + .initialize_as_privileged(&multi_config), ) .combine_results( self.bootstrapper .as_mut() - .initialize_as_privileged(¶ms.multi_config), + .initialize_as_privileged(&multi_config), ); self.privilege_dropper - .chown(¶ms.data_directory, ¶ms.real_user); + .chown(Path::new(data_directory.as_str()), &real_user); - self.privilege_dropper.drop_privileges(¶ms.real_user); + self.privilege_dropper.drop_privileges(&real_user); result .combine_results( self.dns_socket_server .as_mut() - .initialize_as_unprivileged(¶ms.multi_config, streams), + .initialize_as_unprivileged(&multi_config, streams), ) .combine_results( self.bootstrapper .as_mut() - .initialize_as_unprivileged(¶ms.multi_config, streams), + .initialize_as_unprivileged(&multi_config, streams), ) } as_any_ref_in_trait_impl!(); @@ -115,29 +119,6 @@ impl ResultsCombiner for RunModeResult { } } -pub struct GatheredParams<'a> { - pub multi_config: MultiConfig<'a>, - pub config_file_path: PathBuf, - pub real_user: RealUser, - pub data_directory: PathBuf, -} - -impl<'a> GatheredParams<'a> { - pub fn new( - multi_config: MultiConfig<'a>, - config_file_path: PathBuf, - real_user: RealUser, - data_directory: PathBuf, - ) -> Self { - Self { - multi_config, - config_file_path, - real_user, - data_directory, - } - } -} - lazy_static! { pub static ref LOGFILE_NAME: Mutex = Mutex::new(PathBuf::from("uninitialized")); } diff --git a/node/tests/utils.rs b/node/tests/utils.rs index b8bf69fb3..e397af6fe 100644 --- a/node/tests/utils.rs +++ b/node/tests/utils.rs @@ -273,6 +273,8 @@ impl MASQNode { #[allow(dead_code)] pub fn wait_for_exit(&mut self) -> Option { + // TODO Put the body of this function in a background thread and wait on the thread for a few + // seconds. If the thread doesn't terminate, leak the thread and return None. let child_opt = self.child.take(); let output_opt = self.output.take(); match (child_opt, output_opt) {