diff --git a/sn_node_manager/src/add_services/config.rs b/sn_node_manager/src/add_services/config.rs index 79295aac6d..83b34a17be 100644 --- a/sn_node_manager/src/add_services/config.rs +++ b/sn_node_manager/src/add_services/config.rs @@ -23,20 +23,46 @@ pub enum PortRange { Range(u16, u16), } -pub fn parse_port_range(s: &str) -> Result { - if let Ok(port) = u16::from_str(s) { - Ok(PortRange::Single(port)) - } else { - let parts: Vec<&str> = s.split('-').collect(); - if parts.len() != 2 { - return Err(eyre!("Port range must be in the format 'start-end'")); +impl PortRange { + pub fn parse(s: &str) -> Result { + if let Ok(port) = u16::from_str(s) { + Ok(Self::Single(port)) + } else { + let parts: Vec<&str> = s.split('-').collect(); + if parts.len() != 2 { + return Err(eyre!("Port range must be in the format 'start-end'")); + } + let start = parts[0].parse::()?; + let end = parts[1].parse::()?; + if start >= end { + return Err(eyre!("End port must be greater than start port")); + } + Ok(Self::Range(start, end)) } - let start = parts[0].parse::()?; - let end = parts[1].parse::()?; - if start >= end { - return Err(eyre!("End port must be greater than start port")); + } + + /// Validate the port range against a count to make sure the correct number of ports are provided. + pub fn validate(&self, count: u16) -> Result<()> { + match self { + Self::Single(_) => { + if count != 1 { + error!("The count ({count}) does not match the number of ports (1)"); + return Err(eyre!( + "The count ({count}) does not match the number of ports (1)" + )); + } + } + Self::Range(start, end) => { + let port_count = end - start + 1; + if count != port_count { + error!("The count ({count}) does not match the number of ports ({port_count})"); + return Err(eyre!( + "The count ({count}) does not match the number of ports ({port_count})" + )); + } + } } - Ok(PortRange::Range(start, end)) + Ok(()) } } diff --git a/sn_node_manager/src/add_services/mod.rs b/sn_node_manager/src/add_services/mod.rs index 03df580249..a1657640ee 100644 --- a/sn_node_manager/src/add_services/mod.rs +++ b/sn_node_manager/src/add_services/mod.rs @@ -12,10 +12,11 @@ mod tests; use self::config::{ AddAuditorServiceOptions, AddDaemonServiceOptions, AddFaucetServiceOptions, AddNodeServiceOptions, InstallAuditorServiceCtxBuilder, InstallFaucetServiceCtxBuilder, - InstallNodeServiceCtxBuilder, PortRange, + InstallNodeServiceCtxBuilder, }; use crate::{ config::{create_owned_dir, get_user_safenode_data_dir}, + helpers::{check_port_availability, get_start_port_if_applicable, increment_port_option}, VerbosityLevel, DAEMON_SERVICE_NAME, }; use color_eyre::{ @@ -62,39 +63,18 @@ pub async fn add_node( } } - if let Some(ref port_range) = options.node_port { - match port_range { - PortRange::Single(_) => { - let count = options.count.unwrap_or(1); - if count != 1 { - error!("The number of services to add ({count}) does not match the number of ports (1)"); - return Err(eyre!( - "The number of services to add ({count}) does not match the number of ports (1)" - )); - } - } - PortRange::Range(start, end) => { - let port_count = end - start + 1; - let service_count = options.count.unwrap_or(1); - if port_count != service_count { - error!("The number of services to add ({service_count}) does not match the number of ports ({port_count})"); - return Err(eyre!( - "The number of services to add ({service_count}) does not match the number of ports ({port_count})" - )); - } - } - } - } - if let Some(port_option) = &options.node_port { + port_option.validate(options.count.unwrap_or(1))?; check_port_availability(port_option, &node_registry.nodes)?; } if let Some(port_option) = &options.metrics_port { + port_option.validate(options.count.unwrap_or(1))?; check_port_availability(port_option, &node_registry.nodes)?; } if let Some(port_option) = &options.rpc_port { + port_option.validate(options.count.unwrap_or(1))?; check_port_availability(port_option, &node_registry.nodes)?; } @@ -575,52 +555,3 @@ pub fn add_faucet( } } } - -fn get_start_port_if_applicable(range: Option) -> Option { - if let Some(port) = range { - match port { - PortRange::Single(val) => return Some(val), - PortRange::Range(start, _) => return Some(start), - } - } - None -} - -fn increment_port_option(port: Option) -> Option { - if let Some(port) = port { - let incremented_port = port + 1; - return Some(incremented_port); - } - None -} - -fn check_port_availability(port_option: &PortRange, nodes: &[NodeServiceData]) -> Result<()> { - let mut all_ports = Vec::new(); - for node in nodes { - if let Some(port) = node.metrics_port { - all_ports.push(port); - } - if let Some(port) = node.node_port { - all_ports.push(port); - } - all_ports.push(node.rpc_socket_addr.port()); - } - - match port_option { - PortRange::Single(port) => { - if all_ports.iter().any(|p| *p == *port) { - error!("Port {port} is being used by another service"); - return Err(eyre!("Port {port} is being used by another service")); - } - } - PortRange::Range(start, end) => { - for i in *start..=*end { - if all_ports.iter().any(|p| *p == i) { - error!("Port {i} is being used by another service"); - return Err(eyre!("Port {i} is being used by another service")); - } - } - } - } - Ok(()) -} diff --git a/sn_node_manager/src/add_services/tests.rs b/sn_node_manager/src/add_services/tests.rs index 1cfe8e959c..bf1649df4c 100644 --- a/sn_node_manager/src/add_services/tests.rs +++ b/sn_node_manager/src/add_services/tests.rs @@ -1568,7 +1568,7 @@ async fn add_node_should_return_an_error_if_port_and_node_count_do_not_match() - Ok(_) => panic!("This test should result in an error"), Err(e) => { assert_eq!( - format!("The number of services to add (2) does not match the number of ports (3)"), + format!("The count (2) does not match the number of ports (3)"), e.to_string() ) } @@ -1639,7 +1639,7 @@ async fn add_node_should_return_an_error_if_multiple_services_are_specified_with Ok(_) => panic!("This test should result in an error"), Err(e) => { assert_eq!( - format!("The number of services to add (2) does not match the number of ports (1)"), + format!("The count (2) does not match the number of ports (1)"), e.to_string() ) } @@ -2514,7 +2514,7 @@ async fn add_node_should_return_an_error_if_duplicate_custom_rpc_port_in_range_i auto_restart: false, auto_set_nat_flags: false, bootstrap_peers: vec![], - count: None, + count: Some(2), delete_safenode_src: true, enable_metrics_server: false, env_variables: None, diff --git a/sn_node_manager/src/bin/cli/main.rs b/sn_node_manager/src/bin/cli/main.rs index 4598ea8f4d..e57c820d0a 100644 --- a/sn_node_manager/src/bin/cli/main.rs +++ b/sn_node_manager/src/bin/cli/main.rs @@ -11,7 +11,7 @@ use color_eyre::{eyre::eyre, Result}; use libp2p::Multiaddr; use sn_logging::{LogBuilder, LogFormat}; use sn_node_manager::{ - add_services::config::{parse_port_range, PortRange}, + add_services::config::PortRange, cmd::{self}, VerbosityLevel, }; @@ -93,6 +93,9 @@ pub enum SubCmd { data_dir_path: Option, /// Set this flag to enable the metrics server. The ports will be selected at random. /// + /// If you're passing the compiled safenode via --path, make sure to enable the open-metrics feature + /// when compiling. + /// /// If you want to specify the ports, use the --metrics-port argument. #[clap(long)] enable_metrics_server: bool, @@ -132,8 +135,8 @@ pub enum SubCmd { log_format: Option, /// Specify a port for the open metrics server. /// - /// This argument should only be used with a safenode binary that has the open-metrics - /// feature enabled. + /// If you're passing the compiled safenode via --node-path, make sure to enable the open-metrics feature + /// when compiling. /// /// If not set, metrics server will not be started. Use --enable-metrics-server to start /// the metrics server without specifying a port. @@ -141,7 +144,7 @@ pub enum SubCmd { /// If multiple services are being added and this argument is used, you must specify a /// range. For example, '12000-12004'. The length of the range must match the number of /// services, which in this case would be 5. The range must also go from lower to higher. - #[clap(long, value_parser = parse_port_range)] + #[clap(long, value_parser = PortRange::parse)] metrics_port: Option, /// Specify a port for the safenode service(s). /// @@ -150,7 +153,7 @@ pub enum SubCmd { /// If multiple services are being added and this argument is used, you must specify a /// range. For example, '12000-12004'. The length of the range must match the number of /// services, which in this case would be 5. The range must also go from lower to higher. - #[clap(long, value_parser = parse_port_range)] + #[clap(long, value_parser = PortRange::parse)] node_port: Option, /// Provide a path for the safenode binary to be used by the service. /// @@ -182,7 +185,7 @@ pub enum SubCmd { /// If multiple services are being added and this argument is used, you must specify a /// range. For example, '12000-12004'. The length of the range must match the number of /// services, which in this case would be 5. The range must also go from lower to higher. - #[clap(long, value_parser = parse_port_range)] + #[clap(long, value_parser = PortRange::parse)] rpc_port: Option, /// Try to use UPnP to open a port in the home router and allow incoming connections. /// @@ -728,6 +731,15 @@ pub enum LocalSubCmd { /// The number of nodes to run. #[clap(long, default_value_t = DEFAULT_NODE_COUNT)] count: u16, + /// Set this flag to enable the metrics server. The ports will be selected at random. + /// + /// If you're passing the compiled safenode via --node-path, make sure to enable the open-metrics feature flag + /// on the safenode when compiling. If you're using --build, then make sure to enable the feature flag on the + /// safenode-manager. + /// + /// If you want to specify the ports, use the --metrics-port argument. + #[clap(long)] + enable_metrics_server: bool, /// Path to a faucet binary /// /// The path and version arguments are mutually exclusive. @@ -752,11 +764,36 @@ pub enum LocalSubCmd { /// If the argument is not used, the default format will be applied. #[clap(long, value_parser = LogFormat::parse_from_str, verbatim_doc_comment)] log_format: Option, - /// Path to a safenode binary + /// Specify a port for the open metrics server. + /// + /// If you're passing the compiled safenode via --node-path, make sure to enable the open-metrics feature flag + /// on the safenode when compiling. If you're using --build, then make sure to enable the feature flag on the + /// safenode-manager. + /// + /// If not set, metrics server will not be started. Use --enable-metrics-server to start + /// the metrics server without specifying a port. + /// + /// If multiple services are being added and this argument is used, you must specify a + /// range. For example, '12000-12004'. The length of the range must match the number of + /// services, which in this case would be 5. The range must also go from lower to higher. + #[clap(long, value_parser = PortRange::parse)] + metrics_port: Option, + /// Path to a safenode binary. + /// + /// Make sure to enable the local-discovery feature flag on the safenode when compiling the binary. /// /// The path and version arguments are mutually exclusive. #[clap(long, conflicts_with = "node_version")] node_path: Option, + /// Specify a port for the safenode service(s). + /// + /// If not used, ports will be selected at random. + /// + /// If multiple services are being added and this argument is used, you must specify a + /// range. For example, '12000-12004'. The length of the range must match the number of + /// services, which in this case would be 5. The range must also go from lower to higher. + #[clap(long, value_parser = PortRange::parse)] + node_port: Option, /// The version of safenode to use. /// /// The version number should be in the form X.Y.Z, with no 'v' prefix. @@ -779,6 +816,15 @@ pub enum LocalSubCmd { /// The argument exists to support testing scenarios. #[clap(long, conflicts_with = "owner")] owner_prefix: Option, + /// Specify a port for the RPC service(s). + /// + /// If not used, ports will be selected at random. + /// + /// If multiple services are being added and this argument is used, you must specify a + /// range. For example, '12000-12004'. The length of the range must match the number of + /// services, which in this case would be 5. The range must also go from lower to higher. + #[clap(long, value_parser = PortRange::parse)] + rpc_port: Option, /// Set to skip the network validation process #[clap(long)] skip_validation: bool, @@ -803,6 +849,15 @@ pub enum LocalSubCmd { /// The number of nodes to run. #[clap(long, default_value_t = DEFAULT_NODE_COUNT)] count: u16, + /// Set this flag to enable the metrics server. The ports will be selected at random. + /// + /// If you're passing the compiled safenode via --node-path, make sure to enable the open-metrics feature flag + /// on the safenode when compiling. If you're using --build, then make sure to enable the feature flag on the + /// safenode-manager. + /// + /// If you want to specify the ports, use the --metrics-port argument. + #[clap(long)] + enable_metrics_server: bool, /// Path to a faucet binary. /// /// The path and version arguments are mutually exclusive. @@ -827,11 +882,36 @@ pub enum LocalSubCmd { /// If the argument is not used, the default format will be applied. #[clap(long, value_parser = LogFormat::parse_from_str, verbatim_doc_comment)] log_format: Option, + /// Specify a port for the open metrics server. + /// + /// If you're passing the compiled safenode via --node-path, make sure to enable the open-metrics feature flag + /// on the safenode when compiling. If you're using --build, then make sure to enable the feature flag on the + /// safenode-manager. + /// + /// If not set, metrics server will not be started. Use --enable-metrics-server to start + /// the metrics server without specifying a port. + /// + /// If multiple services are being added and this argument is used, you must specify a + /// range. For example, '12000-12004'. The length of the range must match the number of + /// services, which in this case would be 5. The range must also go from lower to higher. + #[clap(long, value_parser = PortRange::parse)] + metrics_port: Option, /// Path to a safenode binary /// + /// Make sure to enable the local-discovery feature flag on the safenode when compiling the binary. + /// /// The path and version arguments are mutually exclusive. #[clap(long, conflicts_with = "node_version", conflicts_with = "build")] node_path: Option, + /// Specify a port for the safenode service(s). + /// + /// If not used, ports will be selected at random. + /// + /// If multiple services are being added and this argument is used, you must specify a + /// range. For example, '12000-12004'. The length of the range must match the number of + /// services, which in this case would be 5. The range must also go from lower to higher. + #[clap(long, value_parser = PortRange::parse)] + node_port: Option, /// The version of safenode to use. /// /// The version number should be in the form X.Y.Z, with no 'v' prefix. @@ -853,6 +933,15 @@ pub enum LocalSubCmd { #[clap(long)] #[clap(long, conflicts_with = "owner")] owner_prefix: Option, + /// Specify a port for the RPC service(s). + /// + /// If not used, ports will be selected at random. + /// + /// If multiple services are being added and this argument is used, you must specify a + /// range. For example, '12000-12004'. The length of the range must match the number of + /// services, which in this case would be 5. The range must also go from lower to higher. + #[clap(long, value_parser = PortRange::parse)] + rpc_port: Option, /// Set to skip the network validation process #[clap(long)] skip_validation: bool, @@ -1034,29 +1123,37 @@ async fn main() -> Result<()> { LocalSubCmd::Join { build, count, + enable_metrics_server, faucet_path, faucet_version, interval, + metrics_port, node_path, + node_port, node_version, log_format, owner, owner_prefix, peers, + rpc_port, skip_validation: _, } => { cmd::local::join( build, count, + enable_metrics_server, faucet_path, faucet_version, interval, + metrics_port, node_path, + node_port, node_version, log_format, owner, owner_prefix, peers, + rpc_port, true, verbosity, ) @@ -1067,28 +1164,36 @@ async fn main() -> Result<()> { build, clean, count, + enable_metrics_server, faucet_path, faucet_version, interval, - owner, - owner_prefix, + log_format, + metrics_port, node_path, + node_port, node_version, - log_format, + owner, + owner_prefix, + rpc_port, skip_validation: _, } => { cmd::local::run( build, clean, count, + enable_metrics_server, faucet_path, faucet_version, interval, + metrics_port, node_path, + node_port, node_version, log_format, owner, owner_prefix, + rpc_port, true, verbosity, ) diff --git a/sn_node_manager/src/cmd/local.rs b/sn_node_manager/src/cmd/local.rs index ccef08613c..1dccb70cb7 100644 --- a/sn_node_manager/src/cmd/local.rs +++ b/sn_node_manager/src/cmd/local.rs @@ -10,6 +10,7 @@ use super::get_bin_path; use crate::{ + add_services::config::PortRange, local::{kill_network, run_network, LocalNetworkOptions}, print_banner, status_report, VerbosityLevel, }; @@ -25,15 +26,19 @@ use std::path::PathBuf; pub async fn join( build: bool, count: u16, + enable_metrics_server: bool, faucet_path: Option, faucet_version: Option, interval: u64, + metrics_port: Option, node_path: Option, + node_port: Option, node_version: Option, log_format: Option, owner: Option, owner_prefix: Option, peers_args: PeersArgs, + rpc_port: Option, skip_validation: bool, verbosity: VerbosityLevel, ) -> Result<(), Report> { @@ -42,11 +47,18 @@ pub async fn join( } info!("Joining local network"); + if (enable_metrics_server || metrics_port.is_some()) && !cfg!(feature = "open-metrics") && build + { + return Err(eyre!( + "Metrics server is not available. Please enable the open-metrics feature flag. Run the command with the --features open-metrics" + )); + } + let local_node_reg_path = &get_local_node_registry_path()?; let mut local_node_registry = NodeRegistry::load(local_node_reg_path)?; let release_repo = ::default_config(); - let faucet_path = get_bin_path( + let faucet_bin_path = get_bin_path( build, faucet_path, ReleaseType::Faucet, @@ -55,7 +67,7 @@ pub async fn join( verbosity, ) .await?; - let node_path = get_bin_path( + let safenode_bin_path = get_bin_path( build, node_path, ReleaseType::Safenode, @@ -81,14 +93,18 @@ pub async fn join( }, }; let options = LocalNetworkOptions { - faucet_bin_path: faucet_path, + enable_metrics_server, + faucet_bin_path, interval, join: true, + metrics_port, node_count: count, + node_port, owner, owner_prefix, peers, - safenode_bin_path: node_path, + rpc_port, + safenode_bin_path, skip_validation, log_format, }; @@ -117,17 +133,28 @@ pub async fn run( build: bool, clean: bool, count: u16, + enable_metrics_server: bool, faucet_path: Option, faucet_version: Option, interval: u64, + metrics_port: Option, node_path: Option, + node_port: Option, node_version: Option, log_format: Option, owner: Option, owner_prefix: Option, + rpc_port: Option, skip_validation: bool, verbosity: VerbosityLevel, ) -> Result<(), Report> { + if (enable_metrics_server || metrics_port.is_some()) && !cfg!(feature = "open-metrics") && build + { + return Err(eyre!( + "Metrics server is not available. Please enable the open-metrics feature flag. Run the command with the --features open-metrics" + )); + } + // In the clean case, the node registry must be loaded *after* the existing network has // been killed, which clears it out. let local_node_reg_path = &get_local_node_registry_path()?; @@ -158,7 +185,7 @@ pub async fn run( info!("Launching local network"); let release_repo = ::default_config(); - let faucet_path = get_bin_path( + let faucet_bin_path = get_bin_path( build, faucet_path, ReleaseType::Faucet, @@ -167,7 +194,7 @@ pub async fn run( verbosity, ) .await?; - let node_path = get_bin_path( + let safenode_bin_path = get_bin_path( build, node_path, ReleaseType::Safenode, @@ -178,14 +205,18 @@ pub async fn run( .await?; let options = LocalNetworkOptions { - faucet_bin_path: faucet_path, + enable_metrics_server, + faucet_bin_path, join: false, interval, + metrics_port, + node_port, node_count: count, owner, owner_prefix, peers: None, - safenode_bin_path: node_path, + rpc_port, + safenode_bin_path, skip_validation, log_format, }; diff --git a/sn_node_manager/src/helpers.rs b/sn_node_manager/src/helpers.rs index 2e3fae01bf..a841b54e6f 100644 --- a/sn_node_manager/src/helpers.rs +++ b/sn_node_manager/src/helpers.rs @@ -13,6 +13,7 @@ use color_eyre::{ use indicatif::{ProgressBar, ProgressStyle}; use semver::Version; use sn_releases::{get_running_platform, ArchiveType, ReleaseType, SafeReleaseRepoActions}; +use sn_service_management::NodeServiceData; use std::{ io::Read, path::{Path, PathBuf}, @@ -20,7 +21,7 @@ use std::{ sync::Arc, }; -use crate::{config, VerbosityLevel}; +use crate::{add_services::config::PortRange, config, VerbosityLevel}; const MAX_DOWNLOAD_RETRIES: u8 = 3; @@ -325,3 +326,55 @@ pub fn create_temp_dir() -> Result { .inspect_err(|err| error!("Failed to crete temp dir: {err:?}"))?; Ok(new_temp_dir) } + +/// Get the start port from the `PortRange` if applicable. +pub fn get_start_port_if_applicable(range: Option) -> Option { + if let Some(port) = range { + match port { + PortRange::Single(val) => return Some(val), + PortRange::Range(start, _) => return Some(start), + } + } + None +} + +/// Increment the port by 1. +pub fn increment_port_option(port: Option) -> Option { + if let Some(port) = port { + let incremented_port = port + 1; + return Some(incremented_port); + } + None +} + +/// Make sure the port is not already in use by another node. +pub fn check_port_availability(port_option: &PortRange, nodes: &[NodeServiceData]) -> Result<()> { + let mut all_ports = Vec::new(); + for node in nodes { + if let Some(port) = node.metrics_port { + all_ports.push(port); + } + if let Some(port) = node.node_port { + all_ports.push(port); + } + all_ports.push(node.rpc_socket_addr.port()); + } + + match port_option { + PortRange::Single(port) => { + if all_ports.iter().any(|p| *p == *port) { + error!("Port {port} is being used by another service"); + return Err(eyre!("Port {port} is being used by another service")); + } + } + PortRange::Range(start, end) => { + for i in *start..=*end { + if all_ports.iter().any(|p| *p == i) { + error!("Port {i} is being used by another service"); + return Err(eyre!("Port {i} is being used by another service")); + } + } + } + } + Ok(()) +} diff --git a/sn_node_manager/src/local.rs b/sn_node_manager/src/local.rs index aea74eb106..4fc4fbeb97 100644 --- a/sn_node_manager/src/local.rs +++ b/sn_node_manager/src/local.rs @@ -6,7 +6,11 @@ // KIND, either express or implied. Please review the Licences for the specific language governing // permissions and limitations relating to use of the SAFE Network Software. -use crate::helpers::{get_bin_version, get_username}; +use crate::add_services::config::PortRange; +use crate::helpers::{ + check_port_availability, get_bin_version, get_start_port_if_applicable, get_username, + increment_port_option, +}; use color_eyre::eyre::OptionExt; use color_eyre::{eyre::eyre, Result}; use colored::Colorize; @@ -35,10 +39,12 @@ pub trait Launcher { fn launch_faucet(&self, genesis_multiaddr: &Multiaddr) -> Result; fn launch_node( &self, - owner: Option, - rpc_socket_addr: SocketAddr, bootstrap_peers: Vec, log_format: Option, + metrics_port: Option, + node_port: Option, + owner: Option, + rpc_socket_addr: SocketAddr, ) -> Result<()>; fn wait(&self, delay: u64); } @@ -71,10 +77,12 @@ impl Launcher for LocalSafeLauncher { fn launch_node( &self, - owner: Option, - rpc_socket_addr: SocketAddr, bootstrap_peers: Vec, log_format: Option, + metrics_port: Option, + node_port: Option, + owner: Option, + rpc_socket_addr: SocketAddr, ) -> Result<()> { let mut args = Vec::new(); @@ -97,6 +105,16 @@ impl Launcher for LocalSafeLauncher { args.push(log_format.as_str().to_string()); } + if let Some(metrics_port) = metrics_port { + args.push("--metrics-server-port".to_string()); + args.push(metrics_port.to_string()); + } + + if let Some(node_port) = node_port { + args.push("--port".to_string()); + args.push(node_port.to_string()); + } + args.push("--local".to_string()); args.push("--rpc".to_string()); args.push(rpc_socket_addr.to_string()); @@ -186,13 +204,17 @@ pub fn kill_network(node_registry: &NodeRegistry, keep_directories: bool) -> Res } pub struct LocalNetworkOptions { + pub enable_metrics_server: bool, pub faucet_bin_path: PathBuf, pub join: bool, pub interval: u64, + pub metrics_port: Option, + pub node_port: Option, pub node_count: u16, pub owner: Option, pub owner_prefix: Option, pub peers: Option>, + pub rpc_port: Option, pub safenode_bin_path: PathBuf, pub skip_validation: bool, pub log_format: Option, @@ -205,11 +227,32 @@ pub async fn run_network( ) -> Result<()> { info!("Running local network"); + // Check port availability when joining a local network. + if let Some(port_range) = &options.node_port { + port_range.validate(options.node_count)?; + check_port_availability(port_range, &node_registry.nodes)?; + } + + if let Some(port_range) = &options.metrics_port { + port_range.validate(options.node_count)?; + check_port_availability(port_range, &node_registry.nodes)?; + } + + if let Some(port_range) = &options.rpc_port { + port_range.validate(options.node_count)?; + check_port_availability(port_range, &node_registry.nodes)?; + } + let launcher = LocalSafeLauncher { safenode_bin_path: options.safenode_bin_path.to_path_buf(), faucet_bin_path: options.faucet_bin_path.to_path_buf(), }; + let mut node_port = get_start_port_if_applicable(options.node_port); + let mut metrics_port = get_start_port_if_applicable(options.metrics_port); + let mut rpc_port = get_start_port_if_applicable(options.rpc_port); + + // Start the bootstrap node if it doesnt exist. let (bootstrap_peers, start) = if options.join { if let Some(peers) = options.peers { (peers, 1) @@ -222,8 +265,20 @@ pub async fn run_network( (peer, 1) } } else { - let rpc_port = service_control.get_available_port()?; - let rpc_socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_port); + let rpc_free_port = if let Some(port) = rpc_port { + port + } else { + service_control.get_available_port()? + }; + let metrics_free_port = if let Some(port) = metrics_port { + Some(port) + } else if options.enable_metrics_server { + Some(service_control.get_available_port()?) + } else { + None + }; + let rpc_socket_addr = + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_free_port); let rpc_client = RpcClient::from_socket_addr(rpc_socket_addr); let number = (node_registry.nodes.len() as u16) + 1; @@ -232,6 +287,8 @@ pub async fn run_network( RunNodeOptions { bootstrap_peers: vec![], genesis: true, + metrics_port: metrics_free_port, + node_port, interval: options.interval, log_format: options.log_format, number, @@ -247,13 +304,28 @@ pub async fn run_network( let bootstrap_peers = node .listen_addr .ok_or_eyre("The listen address was not set")?; + node_port = increment_port_option(node_port); + metrics_port = increment_port_option(metrics_port); + rpc_port = increment_port_option(rpc_port); (bootstrap_peers, 2) }; node_registry.save()?; for _ in start..=options.node_count { - let rpc_port = service_control.get_available_port()?; - let rpc_socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_port); + let rpc_free_port = if let Some(port) = rpc_port { + port + } else { + service_control.get_available_port()? + }; + let metrics_free_port = if let Some(port) = metrics_port { + Some(port) + } else if options.enable_metrics_server { + Some(service_control.get_available_port()?) + } else { + None + }; + let rpc_socket_addr = + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_free_port); let rpc_client = RpcClient::from_socket_addr(rpc_socket_addr); let number = (node_registry.nodes.len() as u16) + 1; @@ -262,6 +334,8 @@ pub async fn run_network( RunNodeOptions { bootstrap_peers: bootstrap_peers.clone(), genesis: false, + metrics_port: metrics_free_port, + node_port, interval: options.interval, log_format: options.log_format, number, @@ -280,6 +354,10 @@ pub async fn run_network( // `kill` command for the nodes that we did spin up. The `kill` command works on the basis // of what's in the node registry. node_registry.save()?; + + node_port = increment_port_option(node_port); + metrics_port = increment_port_option(metrics_port); + rpc_port = increment_port_option(rpc_port); } if !options.skip_validation { @@ -314,6 +392,8 @@ pub struct RunNodeOptions { pub genesis: bool, pub interval: u64, pub log_format: Option, + pub metrics_port: Option, + pub node_port: Option, pub number: u16, pub owner: Option, pub rpc_socket_addr: SocketAddr, @@ -328,10 +408,12 @@ pub async fn run_node( info!("Launching node {}...", run_options.number); println!("Launching node {}...", run_options.number); launcher.launch_node( - run_options.owner.clone(), - run_options.rpc_socket_addr, run_options.bootstrap_peers.clone(), run_options.log_format, + run_options.metrics_port, + run_options.node_port, + run_options.owner.clone(), + run_options.rpc_socket_addr, )?; launcher.wait(run_options.interval); @@ -355,8 +437,8 @@ pub async fn run_node( local: true, log_dir_path: node_info.log_path, log_format: run_options.log_format, - metrics_port: None, - node_port: None, + metrics_port: run_options.metrics_port, + node_port: run_options.node_port, number: run_options.number, owner: run_options.owner, peer_id: Some(peer_id), @@ -471,9 +553,16 @@ mod tests { let rpc_socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 13000); mock_launcher .expect_launch_node() - .with(eq(None), eq(rpc_socket_addr), eq(vec![]), eq(None)) + .with( + eq(vec![]), + eq(None), + eq(None), + eq(None), + eq(None), + eq(rpc_socket_addr), + ) .times(1) - .returning(|_, _, _, _| Ok(())); + .returning(|_, _, _, _, _, _| Ok(())); mock_launcher .expect_wait() .with(eq(100)) @@ -514,6 +603,8 @@ mod tests { genesis: true, interval: 100, log_format: None, + metrics_port: None, + node_port: None, number: 1, owner: None, rpc_socket_addr,