diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index 918d9f014..1c0979524 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -239,7 +239,7 @@ use config::{Config, ConfigError, File, FileFormat}; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, NoneAsEmptyString}; use thiserror::Error; -use torrust_tracker_located_error::{Located, LocatedError}; +use torrust_tracker_located_error::{DynError, Located, LocatedError}; use torrust_tracker_primitives::{DatabaseDriver, TrackerMode}; /// Information required for loading config @@ -289,7 +289,7 @@ impl Info { fs::read_to_string(config_path) .map_err(|e| Error::UnableToLoadFromConfigFile { - source: (Arc::new(e) as Arc).into(), + source: (Arc::new(e) as DynError).into(), })? .parse() .map_err(|_e: std::convert::Infallible| Error::Infallible)? diff --git a/packages/located-error/src/lib.rs b/packages/located-error/src/lib.rs index bf8618686..49e135600 100644 --- a/packages/located-error/src/lib.rs +++ b/packages/located-error/src/lib.rs @@ -33,6 +33,10 @@ use std::error::Error; use std::panic::Location; use std::sync::Arc; +use log::debug; + +pub type DynError = Arc; + /// A generic wrapper around an error. /// /// Where `E` is the inner error (source error). @@ -90,13 +94,13 @@ where source: Arc::new(self.0), location: Box::new(*std::panic::Location::caller()), }; - log::debug!("{e}"); + debug!("{e}"); e } } #[allow(clippy::from_over_into)] -impl<'a> Into> for Arc { +impl<'a> Into> for DynError { #[track_caller] fn into(self) -> LocatedError<'a, dyn std::error::Error + Send + Sync> { LocatedError { diff --git a/src/app.rs b/src/app.rs index 32c12d74a..3608aa22e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -28,8 +28,7 @@ use tokio::task::JoinHandle; use torrust_tracker_configuration::Configuration; use crate::bootstrap::jobs::{health_check_api, http_tracker, torrent_cleanup, tracker_apis, udp_tracker}; -use crate::core; -use crate::servers::http::Version; +use crate::{core, servers}; /// # Panics /// @@ -68,21 +67,22 @@ pub async fn start(config: Arc, tracker: Arc) -> V udp_tracker_config.bind_address, config.mode ); } else { - jobs.push(udp_tracker::start_job(udp_tracker_config, tracker.clone())); + jobs.push(udp_tracker::start_job(udp_tracker_config, tracker.clone()).await); } } // Start the HTTP blocks for http_tracker_config in &config.http_trackers { - if !http_tracker_config.enabled { - continue; - } - jobs.push(http_tracker::start_job(http_tracker_config, tracker.clone(), Version::V1).await); + if let Some(job) = http_tracker::start_job(http_tracker_config, tracker.clone(), servers::http::Version::V1).await { + jobs.push(job); + }; } // Start HTTP API if config.http_api.enabled { - jobs.push(tracker_apis::start_job(&config.http_api, tracker.clone()).await); + if let Some(job) = tracker_apis::start_job(&config.http_api, tracker.clone(), servers::apis::Version::V1).await { + jobs.push(job); + }; } // Start runners to remove torrents without peers, every interval diff --git a/src/bootstrap/jobs/health_check_api.rs b/src/bootstrap/jobs/health_check_api.rs index 96a703afc..fa15eb904 100644 --- a/src/bootstrap/jobs/health_check_api.rs +++ b/src/bootstrap/jobs/health_check_api.rs @@ -6,16 +6,13 @@ //! The [`health_check_api::start_job`](crate::bootstrap::jobs::health_check_api::start_job) //! function spawns a new asynchronous task, that tasks is the "**launcher**". //! The "**launcher**" starts the actual server and sends a message back -//! to the main application. The main application waits until receives -//! the message [`ApiServerJobStarted`] -//! from the "**launcher**". +//! to the main application. //! //! The "**launcher**" is an intermediary thread that decouples the Health Check //! API server from the process that handles it. //! //! Refer to the [configuration documentation](https://docs.rs/torrust-tracker-configuration) //! for the API configuration options. -use std::net::SocketAddr; use std::sync::Arc; use log::info; @@ -23,19 +20,9 @@ use tokio::sync::oneshot; use tokio::task::JoinHandle; use torrust_tracker_configuration::Configuration; +use super::Started; use crate::servers::health_check_api::server; -/// This is the message that the "launcher" spawned task sends to the main -/// application process to notify the API server was successfully started. -/// -/// > **NOTICE**: it does not mean the API server is ready to receive requests. -/// It only means the new server started. It might take some time to the server -/// to be ready to accept request. -#[derive(Debug)] -pub struct ApiServerJobStarted { - pub bound_addr: SocketAddr, -} - /// This function starts a new Health Check API server with the provided /// configuration. /// @@ -53,13 +40,13 @@ pub async fn start_job(config: Arc) -> JoinHandle<()> { .parse::() .expect("Health Check API bind_address invalid."); - let (tx, rx) = oneshot::channel::(); + let (tx_start, rx_start) = oneshot::channel::(); // Run the API server let join_handle = tokio::spawn(async move { info!("Starting Health Check API server: http://{}", bind_addr); - let handle = server::start(bind_addr, tx, config.clone()); + let handle = server::start(bind_addr, tx_start, config.clone()); if let Ok(()) = handle.await { info!("Health Check API server on http://{} stopped", bind_addr); @@ -67,8 +54,8 @@ pub async fn start_job(config: Arc) -> JoinHandle<()> { }); // Wait until the API server job is running - match rx.await { - Ok(_msg) => info!("Torrust Health Check API server started"), + match rx_start.await { + Ok(msg) => info!("Torrust Health Check API server started on socket: {}", msg.address), Err(e) => panic!("the Health Check API server was dropped: {e}"), } diff --git a/src/bootstrap/jobs/http_tracker.rs b/src/bootstrap/jobs/http_tracker.rs index ecf6bd8ac..cf43c35de 100644 --- a/src/bootstrap/jobs/http_tracker.rs +++ b/src/bootstrap/jobs/http_tracker.rs @@ -7,88 +7,79 @@ //! //! The [`http_tracker::start_job`](crate::bootstrap::jobs::http_tracker::start_job) function spawns a new asynchronous task, //! that tasks is the "**launcher**". The "**launcher**" starts the actual server and sends a message back to the main application. -//! The main application waits until receives the message [`ServerJobStarted`] from the "**launcher**". //! //! The "**launcher**" is an intermediary thread that decouples the HTTP servers from the process that handles it. The HTTP could be used independently in the future. //! In that case it would not need to notify a parent process. +use std::net::SocketAddr; use std::sync::Arc; use axum_server::tls_rustls::RustlsConfig; use log::info; -use tokio::sync::oneshot; use tokio::task::JoinHandle; use torrust_tracker_configuration::HttpTracker; +use super::make_rust_tls; use crate::core; -use crate::servers::http::v1::launcher; +use crate::servers::http::server::{HttpServer, Launcher}; use crate::servers::http::Version; -/// This is the message that the "**launcher**" spawned task sends to the main application process to notify that the HTTP server was successfully started. -/// -/// > **NOTICE**: it does not mean the HTTP server is ready to receive requests. It only means the new server started. It might take some time to the server to be ready to accept request. -#[derive(Debug)] -pub struct ServerJobStarted(); - /// It starts a new HTTP server with the provided configuration and version. /// /// Right now there is only one version but in the future we could support more than one HTTP tracker version at the same time. /// This feature allows supporting breaking changes on `BitTorrent` BEPs. -pub async fn start_job(config: &HttpTracker, tracker: Arc, version: Version) -> JoinHandle<()> { - match version { - Version::V1 => start_v1(config, tracker.clone()).await, - } -} - +/// /// # Panics /// /// It would panic if the `config::HttpTracker` struct would contain inappropriate values. -async fn start_v1(config: &HttpTracker, tracker: Arc) -> JoinHandle<()> { - let bind_addr = config - .bind_address - .parse::() - .expect("Tracker API bind_address invalid."); - let ssl_enabled = config.ssl_enabled; - let ssl_cert_path = config.ssl_cert_path.clone(); - let ssl_key_path = config.ssl_key_path.clone(); - - let (tx, rx) = oneshot::channel::(); - - // Run the API server - let join_handle = tokio::spawn(async move { - if !ssl_enabled { - info!("Starting Torrust HTTP tracker server on: http://{}", bind_addr); - - let handle = launcher::start(bind_addr, tracker); +/// +pub async fn start_job(config: &HttpTracker, tracker: Arc, version: Version) -> Option> { + if config.enabled { + let socket = config + .bind_address + .parse::() + .expect("Tracker API bind_address invalid."); + + let tls = make_rust_tls(config.ssl_enabled, &config.ssl_cert_path, &config.ssl_key_path) + .await + .map(|tls| tls.expect("tls config failed")); + + match version { + Version::V1 => Some(start_v1(socket, tls, tracker.clone()).await), + } + } else { + info!("Note: Not loading Http Tracker Service, Not Enabled in Configuration."); + None + } +} - tx.send(ServerJobStarted()) - .expect("the HTTP tracker server should not be dropped"); +async fn start_v1(socket: SocketAddr, tls: Option, tracker: Arc) -> JoinHandle<()> { + let server = HttpServer::new(Launcher::new(socket, tls)) + .start(tracker) + .await + .expect("Failed to start Server"); - if let Ok(()) = handle.await { - info!("Torrust HTTP tracker server on http://{} stopped", bind_addr); - } - } else if ssl_enabled && ssl_cert_path.is_some() && ssl_key_path.is_some() { - info!("Starting Torrust HTTP tracker server on: https://{}", bind_addr); + tokio::spawn(async move { + server.state.task.await.expect("failed to finish service"); + }) +} - let ssl_config = RustlsConfig::from_pem_file(ssl_cert_path.unwrap(), ssl_key_path.unwrap()) - .await - .unwrap(); +#[cfg(test)] +mod tests { + use std::sync::Arc; - let handle = launcher::start_tls(bind_addr, ssl_config, tracker); + use torrust_tracker_test_helpers::configuration::ephemeral_mode_public; - tx.send(ServerJobStarted()) - .expect("the HTTP tracker server should not be dropped"); + use crate::bootstrap::app::initialize_with_configuration; + use crate::bootstrap::jobs::http_tracker::start_job; + use crate::servers::http::Version; - if let Ok(()) = handle.await { - info!("Torrust HTTP tracker server on https://{} stopped", bind_addr); - } - } - }); + #[tokio::test] + async fn it_should_start_http_tracker() { + let cfg = Arc::new(ephemeral_mode_public()); + let config = &cfg.http_trackers[0]; + let tracker = initialize_with_configuration(&cfg); + let version = Version::V1; - // Wait until the HTTP tracker server job is running - match rx.await { - Ok(_msg) => info!("Torrust HTTP tracker server started"), - Err(e) => panic!("the HTTP tracker server was dropped: {e}"), + start_job(config, tracker, version).await.expect("some https server"); } - - join_handle } diff --git a/src/bootstrap/jobs/mod.rs b/src/bootstrap/jobs/mod.rs index 8c85ba45b..9df69b1be 100644 --- a/src/bootstrap/jobs/mod.rs +++ b/src/bootstrap/jobs/mod.rs @@ -11,3 +11,86 @@ pub mod http_tracker; pub mod torrent_cleanup; pub mod tracker_apis; pub mod udp_tracker; + +/// This is the message that the "launcher" spawned task sends to the main +/// application process to notify the service was successfully started. +/// +#[derive(Debug)] +pub struct Started { + pub address: std::net::SocketAddr, +} + +pub async fn make_rust_tls(enabled: bool, cert: &Option, key: &Option) -> Option> { + if enabled { + if let (Some(cert), Some(key)) = (cert, key) { + info!("Using https: cert path: {cert}."); + info!("Using https: key path: {cert}."); + + Some( + RustlsConfig::from_pem_file(cert, key) + .await + .map_err(|err| Error::BadTlsConfig { + source: (Arc::new(err) as DynError).into(), + }), + ) + } else { + Some(Err(Error::MissingTlsConfig { + location: Location::caller(), + })) + } + } else { + info!("tls not enabled"); + None + } +} + +#[cfg(test)] +mod tests { + + use super::make_rust_tls; + + #[tokio::test] + async fn it_should_error_on_bad_tls_config() { + let (bad_cert_path, bad_key_path) = (Some("bad cert path".to_string()), Some("bad key path".to_string())); + let err = make_rust_tls(true, &bad_cert_path, &bad_key_path) + .await + .expect("tls_was_enabled") + .expect_err("bad_cert_and_key_files"); + + assert!(err + .to_string() + .contains("bad tls config: No such file or directory (os error 2)")); + } + + #[tokio::test] + async fn it_should_error_on_missing_tls_config() { + let err = make_rust_tls(true, &None, &None) + .await + .expect("tls_was_enabled") + .expect_err("missing_config"); + + assert_eq!(err.to_string(), "tls config missing"); + } +} + +use std::panic::Location; +use std::sync::Arc; + +use axum_server::tls_rustls::RustlsConfig; +use log::info; +use thiserror::Error; +use torrust_tracker_located_error::{DynError, LocatedError}; + +/// Error returned by the Bootstrap Process. +#[derive(Error, Debug)] +pub enum Error { + /// Enabled tls but missing config. + #[error("tls config missing")] + MissingTlsConfig { location: &'static Location<'static> }, + + /// Unable to parse tls Config. + #[error("bad tls config: {source}")] + BadTlsConfig { + source: LocatedError<'static, dyn std::error::Error + Send + Sync>, + }, +} diff --git a/src/bootstrap/jobs/tracker_apis.rs b/src/bootstrap/jobs/tracker_apis.rs index ca29d2b5f..e4ba0f848 100644 --- a/src/bootstrap/jobs/tracker_apis.rs +++ b/src/bootstrap/jobs/tracker_apis.rs @@ -20,16 +20,18 @@ //! //! Refer to the [configuration documentation](https://docs.rs/torrust-tracker-configuration) //! for the API configuration options. +use std::net::SocketAddr; use std::sync::Arc; use axum_server::tls_rustls::RustlsConfig; use log::info; -use tokio::sync::oneshot; use tokio::task::JoinHandle; use torrust_tracker_configuration::HttpApi; +use super::make_rust_tls; use crate::core; -use crate::servers::apis::server; +use crate::servers::apis::server::{ApiServer, Launcher}; +use crate::servers::apis::Version; /// This is the message that the "launcher" spawned task sends to the main /// application process to notify the API server was successfully started. @@ -49,51 +51,56 @@ pub struct ApiServerJobStarted(); /// # Panics /// /// It would panic if unable to send the `ApiServerJobStarted` notice. -pub async fn start_job(config: &HttpApi, tracker: Arc) -> JoinHandle<()> { - let bind_addr = config - .bind_address - .parse::() - .expect("Tracker API bind_address invalid."); - let ssl_enabled = config.ssl_enabled; - let ssl_cert_path = config.ssl_cert_path.clone(); - let ssl_key_path = config.ssl_key_path.clone(); - - let (tx, rx) = oneshot::channel::(); +/// +/// +pub async fn start_job(config: &HttpApi, tracker: Arc, version: Version) -> Option> { + if config.enabled { + let bind_to = config + .bind_address + .parse::() + .expect("Tracker API bind_address invalid."); - // Run the API server - let join_handle = tokio::spawn(async move { - if !ssl_enabled { - info!("Starting Torrust APIs server on: http://{}", bind_addr); + let tls = make_rust_tls(config.ssl_enabled, &config.ssl_cert_path, &config.ssl_key_path) + .await + .map(|tls| tls.expect("tls config failed")); - let handle = server::start(bind_addr, tracker); + match version { + Version::V1 => Some(start_v1(bind_to, tls, tracker.clone()).await), + } + } else { + info!("Note: Not loading Http Tracker Service, Not Enabled in Configuration."); + None + } +} - tx.send(ApiServerJobStarted()).expect("the API server should not be dropped"); +async fn start_v1(socket: SocketAddr, tls: Option, tracker: Arc) -> JoinHandle<()> { + let server = ApiServer::new(Launcher::new(socket, tls)) + .start(tracker) + .await + .expect("Failed to start Server"); - if let Ok(()) = handle.await { - info!("Torrust APIs server on http://{} stopped", bind_addr); - } - } else if ssl_enabled && ssl_cert_path.is_some() && ssl_key_path.is_some() { - info!("Starting Torrust APIs server on: https://{}", bind_addr); + tokio::spawn(async move { + server.state.task.await.expect("failed to close service"); + }) +} - let ssl_config = RustlsConfig::from_pem_file(ssl_cert_path.unwrap(), ssl_key_path.unwrap()) - .await - .unwrap(); +#[cfg(test)] +mod tests { + use std::sync::Arc; - let handle = server::start_tls(bind_addr, ssl_config, tracker); + use torrust_tracker_test_helpers::configuration::ephemeral_mode_public; - tx.send(ApiServerJobStarted()).expect("the API server should not be dropped"); + use crate::bootstrap::app::initialize_with_configuration; + use crate::bootstrap::jobs::tracker_apis::start_job; + use crate::servers::apis::Version; - if let Ok(()) = handle.await { - info!("Torrust APIs server on https://{} stopped", bind_addr); - } - } - }); + #[tokio::test] + async fn it_should_start_http_tracker() { + let cfg = Arc::new(ephemeral_mode_public()); + let config = &cfg.http_api; + let tracker = initialize_with_configuration(&cfg); + let version = Version::V1; - // Wait until the APIs server job is running - match rx.await { - Ok(_msg) => info!("Torrust APIs server started"), - Err(e) => panic!("the API server was dropped: {e}"), + start_job(config, tracker, version).await.expect("some https server"); } - - join_handle } diff --git a/src/bootstrap/jobs/udp_tracker.rs b/src/bootstrap/jobs/udp_tracker.rs index 9a30c9126..b0c3d8f0f 100644 --- a/src/bootstrap/jobs/udp_tracker.rs +++ b/src/bootstrap/jobs/udp_tracker.rs @@ -8,30 +8,34 @@ //! for the configuration options. use std::sync::Arc; -use log::{error, info, warn}; use tokio::task::JoinHandle; use torrust_tracker_configuration::UdpTracker; use crate::core; -use crate::servers::udp::server::Udp; +use crate::servers::udp::server::{Launcher, UdpServer}; /// It starts a new UDP server with the provided configuration. /// /// It spawns a new asynchronous task for the new UDP server. +/// +/// # Panics +/// +/// It will panic if the API binding address is not a valid socket. +/// It will panic if it is unable to start the UDP service. +/// It will panic if the task did not finish successfully. #[must_use] -pub fn start_job(config: &UdpTracker, tracker: Arc) -> JoinHandle<()> { - let bind_addr = config.bind_address.clone(); +pub async fn start_job(config: &UdpTracker, tracker: Arc) -> JoinHandle<()> { + let bind_to = config + .bind_address + .parse::() + .expect("Tracker API bind_address invalid."); + + let server = UdpServer::new(Launcher::new(bind_to)) + .start(tracker) + .await + .expect("unable to start udp service"); tokio::spawn(async move { - match Udp::new(&bind_addr).await { - Ok(udp_server) => { - info!("Starting UDP server on: udp://{}", bind_addr); - udp_server.start(tracker).await; - } - Err(e) => { - warn!("Could not start UDP tracker on: udp://{}", bind_addr); - error!("{}", e); - } - } + server.state.task.await.expect("task didn't finish"); }) } diff --git a/src/core/auth.rs b/src/core/auth.rs index c6b772485..9fc9d6e7b 100644 --- a/src/core/auth.rs +++ b/src/core/auth.rs @@ -47,7 +47,7 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use torrust_tracker_located_error::LocatedError; +use torrust_tracker_located_error::{DynError, LocatedError}; use crate::shared::bit_torrent::common::AUTH_KEY_LENGTH; use crate::shared::clock::{convert_from_timestamp_to_datetime_utc, Current, DurationSinceUnixEpoch, Time, TimeNow}; @@ -185,7 +185,7 @@ pub enum Error { impl From for Error { fn from(e: r2d2_sqlite::rusqlite::Error) -> Self { Error::KeyVerificationError { - source: (Arc::new(e) as Arc).into(), + source: (Arc::new(e) as DynError).into(), } } } diff --git a/src/core/databases/error.rs b/src/core/databases/error.rs index 96b0d835e..a5179e3a4 100644 --- a/src/core/databases/error.rs +++ b/src/core/databases/error.rs @@ -5,7 +5,7 @@ use std::panic::Location; use std::sync::Arc; use r2d2_mysql::mysql::UrlError; -use torrust_tracker_located_error::{Located, LocatedError}; +use torrust_tracker_located_error::{DynError, Located, LocatedError}; use torrust_tracker_primitives::DatabaseDriver; #[derive(thiserror::Error, Debug, Clone)] @@ -59,11 +59,11 @@ impl From for Error { fn from(err: r2d2_sqlite::rusqlite::Error) -> Self { match err { r2d2_sqlite::rusqlite::Error::QueryReturnedNoRows => Error::QueryReturnedNoRows { - source: (Arc::new(err) as Arc).into(), + source: (Arc::new(err) as DynError).into(), driver: DatabaseDriver::Sqlite3, }, _ => Error::InvalidQuery { - source: (Arc::new(err) as Arc).into(), + source: (Arc::new(err) as DynError).into(), driver: DatabaseDriver::Sqlite3, }, } @@ -73,7 +73,7 @@ impl From for Error { impl From for Error { #[track_caller] fn from(err: r2d2_mysql::mysql::Error) -> Self { - let e: Arc = Arc::new(err); + let e: DynError = Arc::new(err); Error::InvalidQuery { source: e.into(), driver: DatabaseDriver::MySQL, diff --git a/src/servers/apis/mod.rs b/src/servers/apis/mod.rs index 5f8c581d0..2d4b3abe1 100644 --- a/src/servers/apis/mod.rs +++ b/src/servers/apis/mod.rs @@ -159,8 +159,6 @@ pub mod routes; pub mod server; pub mod v1; -use serde::Deserialize; - /// The info hash URL path parameter. /// /// Some API endpoints require an info hash as a path parameter. @@ -172,3 +170,12 @@ use serde::Deserialize; /// in order to provide a more specific error message. #[derive(Deserialize)] pub struct InfoHashParam(pub String); + +use serde::{Deserialize, Serialize}; + +/// The version of the HTTP Api. +#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug)] +pub enum Version { + /// The `v1` version of the HTTP Api. + V1, +} diff --git a/src/servers/apis/server.rs b/src/servers/apis/server.rs index c42083f9f..a2ecd616d 100644 --- a/src/servers/apis/server.rs +++ b/src/servers/apis/server.rs @@ -24,18 +24,18 @@ /// for example, to restart it to apply new configuration changes, to remotely /// shutdown the server, etc. use std::net::SocketAddr; -use std::str::FromStr; use std::sync::Arc; use axum_server::tls_rustls::RustlsConfig; use axum_server::Handle; +use derive_more::Constructor; use futures::future::BoxFuture; -use futures::Future; -use log::info; +use tokio::sync::oneshot::{Receiver, Sender}; use super::routes::router; +use crate::bootstrap::jobs::Started; use crate::core::Tracker; -use crate::servers::signals::shutdown_signal; +use crate::servers::signals::{graceful_shutdown, Halted}; /// Errors that can occur when starting or stopping the API server. #[derive(Debug)] @@ -58,24 +58,27 @@ pub type RunningApiServer = ApiServer; /// states: `Stopped` or `Running`. #[allow(clippy::module_name_repetitions)] pub struct ApiServer { - pub cfg: torrust_tracker_configuration::HttpApi, pub state: S, } /// The `Stopped` state of the `ApiServer` struct. -pub struct Stopped; +pub struct Stopped { + launcher: Launcher, +} /// The `Running` state of the `ApiServer` struct. pub struct Running { - pub bind_addr: SocketAddr, - task_killer: tokio::sync::oneshot::Sender, - task: tokio::task::JoinHandle<()>, + pub binding: SocketAddr, + pub halt_task: tokio::sync::oneshot::Sender, + pub task: tokio::task::JoinHandle, } impl ApiServer { #[must_use] - pub fn new(cfg: torrust_tracker_configuration::HttpApi) -> Self { - Self { cfg, state: Stopped {} } + pub fn new(launcher: Launcher) -> Self { + Self { + state: Stopped { launcher }, + } } /// Starts the API server with the given configuration. @@ -88,28 +91,20 @@ impl ApiServer { /// /// It would panic if the bound socket address cannot be sent back to this starter. pub async fn start(self, tracker: Arc) -> Result, Error> { - let (shutdown_sender, shutdown_receiver) = tokio::sync::oneshot::channel::(); - let (addr_sender, addr_receiver) = tokio::sync::oneshot::channel::(); + let (tx_start, rx_start) = tokio::sync::oneshot::channel::(); + let (tx_halt, rx_halt) = tokio::sync::oneshot::channel::(); - let configuration = self.cfg.clone(); + let launcher = self.state.launcher; let task = tokio::spawn(async move { - let (bind_addr, server) = Launcher::start(&configuration, tracker, shutdown_signal(shutdown_receiver)); - - addr_sender.send(bind_addr).expect("Could not return SocketAddr."); - - server.await; + launcher.start(tracker, tx_start, rx_halt).await; + launcher }); - let bind_address = addr_receiver - .await - .map_err(|_| Error::Error("Could not receive bind_address.".to_string()))?; - Ok(ApiServer { - cfg: self.cfg, state: Running { - bind_addr: bind_address, - task_killer: shutdown_sender, + binding: rx_start.await.expect("unable to start service").address, + halt_task: tx_halt, task, }, }) @@ -124,21 +119,24 @@ impl ApiServer { /// It would return an error if the channel for the task killer signal was closed. pub async fn stop(self) -> Result, Error> { self.state - .task_killer - .send(0) + .halt_task + .send(Halted::Normal) .map_err(|_| Error::Error("Task killer channel was closed.".to_string()))?; - drop(self.state.task.await); + let launcher = self.state.task.await.map_err(|e| Error::Error(e.to_string()))?; Ok(ApiServer { - cfg: self.cfg, - state: Stopped {}, + state: Stopped { launcher }, }) } } /// A struct responsible for starting the API server. -struct Launcher; +#[derive(Constructor, Debug)] +pub struct Launcher { + bind_to: SocketAddr, + tls: Option, +} impl Launcher { /// Starts the API server with graceful shutdown. @@ -146,175 +144,78 @@ impl Launcher { /// If TLS is enabled in the configuration, it will start the server with /// TLS. See [`torrust-tracker-configuration`](torrust_tracker_configuration) /// for more information about configuration. - pub fn start( - cfg: &torrust_tracker_configuration::HttpApi, - tracker: Arc, - shutdown_signal: F, - ) -> (SocketAddr, BoxFuture<'static, ()>) - where - F: Future + Send + 'static, - { - let addr = SocketAddr::from_str(&cfg.bind_address).expect("bind_address is not a valid SocketAddr."); - let tcp_listener = std::net::TcpListener::bind(addr).expect("Could not bind tcp_listener to address."); - let bind_addr = tcp_listener - .local_addr() - .expect("Could not get local_addr from tcp_listener."); - - if let (true, Some(ssl_cert_path), Some(ssl_key_path)) = (&cfg.ssl_enabled, &cfg.ssl_cert_path, &cfg.ssl_key_path) { - let server = Self::start_tls_with_graceful_shutdown( - tcp_listener, - (ssl_cert_path.to_string(), ssl_key_path.to_string()), - tracker, - shutdown_signal, - ); - - (bind_addr, server) - } else { - let server = Self::start_with_graceful_shutdown(tcp_listener, tracker, shutdown_signal); - - (bind_addr, server) - } - } - - /// Starts the API server with graceful shutdown. - pub fn start_with_graceful_shutdown( - tcp_listener: std::net::TcpListener, - tracker: Arc, - shutdown_signal: F, - ) -> BoxFuture<'static, ()> - where - F: Future + Send + 'static, - { - let app = router(tracker); - - let handle = Handle::new(); - - let cloned_handle = handle.clone(); - - tokio::task::spawn(async move { - shutdown_signal.await; - cloned_handle.shutdown(); - }); - - Box::pin(async { - axum_server::from_tcp(tcp_listener) - .handle(handle) - .serve(app.into_make_service_with_connect_info::()) - .await - .expect("Axum server crashed."); - }) - } - - /// Starts the API server with graceful shutdown and TLS. - pub fn start_tls_with_graceful_shutdown( - tcp_listener: std::net::TcpListener, - (ssl_cert_path, ssl_key_path): (String, String), - tracker: Arc, - shutdown_signal: F, - ) -> BoxFuture<'static, ()> - where - F: Future + Send + 'static, - { - let app = router(tracker); + /// + /// # Panics + /// + /// Will panic if unable to bind to the socket, or unable to get the address of the bound socket. + /// Will also panic if unable to send message regarding the bound socket address. + pub fn start(&self, tracker: Arc, tx_start: Sender, rx_halt: Receiver) -> BoxFuture<'static, ()> { + let router = router(tracker); + let socket = std::net::TcpListener::bind(self.bind_to).expect("Could not bind tcp_listener to address."); + let address = socket.local_addr().expect("Could not get local_addr from tcp_listener."); let handle = Handle::new(); - let cloned_handle = handle.clone(); - - tokio::task::spawn(async move { - shutdown_signal.await; - cloned_handle.shutdown(); + tokio::task::spawn(graceful_shutdown( + handle.clone(), + rx_halt, + format!("shutting down http server on socket address: {address}"), + )); + + let tls = self.tls.clone(); + + let running = Box::pin(async { + match tls { + Some(tls) => axum_server::from_tcp_rustls(socket, tls) + .handle(handle) + .serve(router.into_make_service_with_connect_info::()) + .await + .expect("Axum server crashed."), + None => axum_server::from_tcp(socket) + .handle(handle) + .serve(router.into_make_service_with_connect_info::()) + .await + .expect("Axum server crashed."), + } }); - Box::pin(async { - let tls_config = RustlsConfig::from_pem_file(ssl_cert_path, ssl_key_path) - .await - .expect("Could not read tls cert."); + tx_start + .send(Started { address }) + .expect("the HTTP(s) Tracker service should not be dropped"); - axum_server::from_tcp_rustls(tcp_listener, tls_config) - .handle(handle) - .serve(app.into_make_service_with_connect_info::()) - .await - .expect("Axum server crashed."); - }) + running } } -/// Starts the API server with graceful shutdown on the current thread. -/// -/// # Panics -/// -/// It would panic if it fails to listen to shutdown signal. -pub fn start(socket_addr: SocketAddr, tracker: Arc) -> impl Future> { - let app = router(tracker); - - let handle = Handle::new(); - let shutdown_handle = handle.clone(); - - tokio::spawn(async move { - tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal."); - info!("Stopping Torrust APIs server on https://{} ...", socket_addr); - shutdown_handle.shutdown(); - }); - - axum_server::bind(socket_addr).handle(handle).serve(app.into_make_service()) -} - -/// Starts the API server with graceful shutdown and TLS on the current thread. -/// -/// # Panics -/// -/// It would panic if it fails to listen to shutdown signal. -pub fn start_tls( - socket_addr: SocketAddr, - ssl_config: RustlsConfig, - tracker: Arc, -) -> impl Future> { - let app = router(tracker); - - let handle = Handle::new(); - let shutdown_handle = handle.clone(); - - tokio::spawn(async move { - tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal."); - info!("Stopping Torrust APIs server on https://{} ...", socket_addr); - shutdown_handle.shutdown(); - }); - - axum_server::bind_rustls(socket_addr, ssl_config) - .handle(handle) - .serve(app.into_make_service()) -} - #[cfg(test)] mod tests { use std::sync::Arc; - use torrust_tracker_configuration::Configuration; - use torrust_tracker_test_helpers::configuration; + use torrust_tracker_test_helpers::configuration::ephemeral_mode_public; - use crate::core; - use crate::core::statistics; - use crate::servers::apis::server::ApiServer; - - fn tracker_configuration() -> Arc { - Arc::new(configuration::ephemeral()) - } + use crate::bootstrap::app::initialize_with_configuration; + use crate::bootstrap::jobs::make_rust_tls; + use crate::servers::apis::server::{ApiServer, Launcher}; #[tokio::test] - async fn it_should_be_able_to_start_from_stopped_state_and_then_stop_again() { - let cfg = tracker_configuration(); - - let tracker = Arc::new(core::Tracker::new(cfg.clone(), None, statistics::Repo::new()).unwrap()); + async fn it_should_be_able_to_start_and_stop() { + let cfg = Arc::new(ephemeral_mode_public()); + let tracker = initialize_with_configuration(&cfg); + let config = &cfg.http_trackers[0]; - let stopped_api_server = ApiServer::new(cfg.http_api.clone()); + let bind_to = config + .bind_address + .parse::() + .expect("Tracker API bind_address invalid."); - let running_api_server_result = stopped_api_server.start(tracker).await; - - assert!(running_api_server_result.is_ok()); + let tls = make_rust_tls(config.ssl_enabled, &config.ssl_cert_path, &config.ssl_key_path) + .await + .map(|tls| tls.expect("tls config failed")); - let running_api_server = running_api_server_result.unwrap(); + let stopped = ApiServer::new(Launcher::new(bind_to, tls)); + let started = stopped.start(tracker).await.expect("it should start the server"); + let stopped = started.stop().await.expect("it should stop the server"); - assert!(running_api_server.stop().await.is_ok()); + assert_eq!(stopped.state.launcher.bind_to, bind_to); } } diff --git a/src/servers/health_check_api/server.rs b/src/servers/health_check_api/server.rs index d4654d617..fb807d09c 100644 --- a/src/servers/health_check_api/server.rs +++ b/src/servers/health_check_api/server.rs @@ -14,7 +14,7 @@ use serde_json::json; use tokio::sync::oneshot::Sender; use torrust_tracker_configuration::Configuration; -use crate::bootstrap::jobs::health_check_api::ApiServerJobStarted; +use crate::bootstrap::jobs::Started; use crate::servers::health_check_api::handlers::health_check_handler; /// Starts Health Check API server. @@ -23,8 +23,8 @@ use crate::servers::health_check_api::handlers::health_check_handler; /// /// Will panic if binding to the socket address fails. pub fn start( - socket_addr: SocketAddr, - tx: Sender, + address: SocketAddr, + tx: Sender, config: Arc, ) -> impl Future> { let app = Router::new() @@ -35,22 +35,20 @@ pub fn start( let handle = Handle::new(); let cloned_handle = handle.clone(); - let tcp_listener = std::net::TcpListener::bind(socket_addr).expect("Could not bind tcp_listener to address."); - let bound_addr = tcp_listener - .local_addr() - .expect("Could not get local_addr from tcp_listener."); + let socket = std::net::TcpListener::bind(address).expect("Could not bind tcp_listener to address."); + let address = socket.local_addr().expect("Could not get local_addr from tcp_listener."); tokio::task::spawn(async move { tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal."); - info!("Stopping Torrust Health Check API server o http://{} ...", bound_addr); + info!("Stopping Torrust Health Check API server o http://{} ...", address); cloned_handle.shutdown(); }); - let running = axum_server::from_tcp(tcp_listener) + let running = axum_server::from_tcp(socket) .handle(handle) .serve(app.into_make_service_with_connect_info::()); - tx.send(ApiServerJobStarted { bound_addr }) + tx.send(Started { address }) .expect("the Health Check API server should not be dropped"); running diff --git a/src/servers/http/server.rs b/src/servers/http/server.rs index 2d8fc745f..aee2d0ac0 100644 --- a/src/servers/http/server.rs +++ b/src/servers/http/server.rs @@ -1,30 +1,17 @@ //! Module to handle the HTTP server instances. -use std::future::Future; use std::net::SocketAddr; use std::sync::Arc; +use axum_server::tls_rustls::RustlsConfig; +use axum_server::Handle; +use derive_more::Constructor; use futures::future::BoxFuture; +use tokio::sync::oneshot::{Receiver, Sender}; +use super::v1::routes::router; +use crate::bootstrap::jobs::Started; use crate::core::Tracker; -use crate::servers::signals::shutdown_signal; - -/// Trait to be implemented by a HTTP server launcher for the tracker. -/// -/// A launcher is responsible for starting the server and returning the -/// `SocketAddr` it is bound to. -#[allow(clippy::module_name_repetitions)] -pub trait HttpServerLauncher: Sync + Send { - fn new() -> Self; - - fn start_with_graceful_shutdown( - &self, - cfg: torrust_tracker_configuration::HttpTracker, - tracker: Arc, - shutdown_signal: F, - ) -> (SocketAddr, BoxFuture<'static, ()>) - where - F: Future + Send + 'static; -} +use crate::servers::signals::{graceful_shutdown, Halted}; /// Error that can occur when starting or stopping the HTTP server. /// @@ -40,17 +27,61 @@ pub trait HttpServerLauncher: Sync + Send { /// completion. #[derive(Debug)] pub enum Error { - /// Any kind of error starting or stopping the server. - Error(String), // todo: refactor to use thiserror and add more variants for specific errors. + Error(String), +} + +#[derive(Constructor, Debug)] +pub struct Launcher { + pub bind_to: SocketAddr, + pub tls: Option, +} + +impl Launcher { + fn start(&self, tracker: Arc, tx_start: Sender, rx_halt: Receiver) -> BoxFuture<'static, ()> { + let app = router(tracker); + let socket = std::net::TcpListener::bind(self.bind_to).expect("Could not bind tcp_listener to address."); + let address = socket.local_addr().expect("Could not get local_addr from tcp_listener."); + + let handle = Handle::new(); + + tokio::task::spawn(graceful_shutdown( + handle.clone(), + rx_halt, + format!("shutting down http server on socket address: {address}"), + )); + + let tls = self.tls.clone(); + + let running = Box::pin(async { + match tls { + Some(tls) => axum_server::from_tcp_rustls(socket, tls) + .handle(handle) + .serve(app.into_make_service_with_connect_info::()) + .await + .expect("Axum server crashed."), + None => axum_server::from_tcp(socket) + .handle(handle) + .serve(app.into_make_service_with_connect_info::()) + .await + .expect("Axum server crashed."), + } + }); + + tx_start + .send(Started { address }) + .expect("the HTTP(s) Tracker service should not be dropped"); + + running + } } /// A HTTP server instance controller with no HTTP instance running. #[allow(clippy::module_name_repetitions)] -pub type StoppedHttpServer = HttpServer>; +pub type StoppedHttpServer = HttpServer; /// A HTTP server instance controller with a running HTTP instance. #[allow(clippy::module_name_repetitions)] -pub type RunningHttpServer = HttpServer>; +pub type RunningHttpServer = HttpServer; /// A HTTP server instance controller. /// @@ -69,31 +100,28 @@ pub type RunningHttpServer = HttpServer>; /// intended to persist configurations between runs. #[allow(clippy::module_name_repetitions)] pub struct HttpServer { - /// The configuration of the server that will be used every time the server - /// is started. - pub cfg: torrust_tracker_configuration::HttpTracker, /// The state of the server: `running` or `stopped`. pub state: S, } /// A stopped HTTP server state. -pub struct Stopped { - launcher: I, +pub struct Stopped { + launcher: Launcher, } /// A running HTTP server state. -pub struct Running { +pub struct Running { /// The address where the server is bound. - pub bind_addr: SocketAddr, - task_killer: tokio::sync::oneshot::Sender, - task: tokio::task::JoinHandle, + pub binding: SocketAddr, + pub halt_task: tokio::sync::oneshot::Sender, + pub task: tokio::task::JoinHandle, } -impl HttpServer> { +impl HttpServer { /// It creates a new `HttpServer` controller in `stopped` state. - pub fn new(cfg: torrust_tracker_configuration::HttpTracker, launcher: I) -> Self { + #[must_use] + pub fn new(launcher: Launcher) -> Self { Self { - cfg, state: Stopped { launcher }, } } @@ -109,57 +137,80 @@ impl HttpServer> { /// /// It would panic spawned HTTP server launcher cannot send the bound `SocketAddr` /// back to the main thread. - pub async fn start(self, tracker: Arc) -> Result>, Error> { - let (shutdown_sender, shutdown_receiver) = tokio::sync::oneshot::channel::(); - let (addr_sender, addr_receiver) = tokio::sync::oneshot::channel::(); + pub async fn start(self, tracker: Arc) -> Result, Error> { + let (tx_start, rx_start) = tokio::sync::oneshot::channel::(); + let (tx_halt, rx_halt) = tokio::sync::oneshot::channel::(); - let configuration = self.cfg.clone(); let launcher = self.state.launcher; let task = tokio::spawn(async move { - let (bind_addr, server) = - launcher.start_with_graceful_shutdown(configuration, tracker, shutdown_signal(shutdown_receiver)); - - addr_sender.send(bind_addr).expect("Could not return SocketAddr."); + let server = launcher.start(tracker, tx_start, rx_halt); server.await; launcher }); - let bind_address = addr_receiver - .await - .map_err(|_| Error::Error("Could not receive bind_address.".to_string()))?; - Ok(HttpServer { - cfg: self.cfg, state: Running { - bind_addr: bind_address, - task_killer: shutdown_sender, + binding: rx_start.await.expect("unable to start service").address, + halt_task: tx_halt, task, }, }) } } -impl HttpServer> { +impl HttpServer { /// It stops the server and returns a `HttpServer` controller in `stopped` /// state. /// /// # Errors /// /// It would return an error if the channel for the task killer signal was closed. - pub async fn stop(self) -> Result>, Error> { + pub async fn stop(self) -> Result, Error> { self.state - .task_killer - .send(0) + .halt_task + .send(Halted::Normal) .map_err(|_| Error::Error("Task killer channel was closed.".to_string()))?; let launcher = self.state.task.await.map_err(|e| Error::Error(e.to_string()))?; Ok(HttpServer { - cfg: self.cfg, state: Stopped { launcher }, }) } } + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use torrust_tracker_test_helpers::configuration::ephemeral_mode_public; + + use crate::bootstrap::app::initialize_with_configuration; + use crate::bootstrap::jobs::make_rust_tls; + use crate::servers::http::server::{HttpServer, Launcher}; + + #[tokio::test] + async fn it_should_be_able_to_start_and_stop() { + let cfg = Arc::new(ephemeral_mode_public()); + let tracker = initialize_with_configuration(&cfg); + let config = &cfg.http_trackers[0]; + + let bind_to = config + .bind_address + .parse::() + .expect("Tracker API bind_address invalid."); + + let tls = make_rust_tls(config.ssl_enabled, &config.ssl_cert_path, &config.ssl_key_path) + .await + .map(|tls| tls.expect("tls config failed")); + + let stopped = HttpServer::new(Launcher::new(bind_to, tls)); + let started = stopped.start(tracker).await.expect("it should start the server"); + let stopped = started.stop().await.expect("it should stop the server"); + + assert_eq!(stopped.state.launcher.bind_to, bind_to); + } +} diff --git a/src/servers/http/v1/launcher.rs b/src/servers/http/v1/launcher.rs deleted file mode 100644 index 6b89e8ce7..000000000 --- a/src/servers/http/v1/launcher.rs +++ /dev/null @@ -1,188 +0,0 @@ -//! Logic to start new HTTP server instances. -use std::future::Future; -use std::net::SocketAddr; -use std::str::FromStr; -use std::sync::Arc; - -use async_trait::async_trait; -use axum_server::tls_rustls::RustlsConfig; -use axum_server::Handle; -use futures::future::BoxFuture; -use log::info; - -use super::routes::router; -use crate::core::Tracker; -use crate::servers::http::server::HttpServerLauncher; - -#[derive(Debug)] -pub enum Error { - Error(String), -} - -pub struct Launcher; - -impl Launcher { - /// It starts a new HTTP server instance from a TCP listener with graceful shutdown. - /// - /// # Panics - /// - /// Will panic if: - /// - /// - The TCP listener could not be bound. - /// - The Axum server crashes. - pub fn start_from_tcp_listener_with_graceful_shutdown( - tcp_listener: std::net::TcpListener, - tracker: Arc, - shutdown_signal: F, - ) -> BoxFuture<'static, ()> - where - F: Future + Send + 'static, - { - let app = router(tracker); - - let handle = Handle::new(); - - let cloned_handle = handle.clone(); - - tokio::task::spawn(async move { - shutdown_signal.await; - cloned_handle.shutdown(); - }); - - Box::pin(async { - axum_server::from_tcp(tcp_listener) - .handle(handle) - .serve(app.into_make_service_with_connect_info::()) - .await - .expect("Axum server crashed."); - }) - } - - /// It starts a new HTTPS server instance from a TCP listener with graceful shutdown. - /// - /// # Panics - /// - /// Will panic if: - /// - /// - The SSL certificate could not be read from the provided path or is invalid. - /// - The Axum server crashes. - pub fn start_tls_from_tcp_listener_with_graceful_shutdown( - tcp_listener: std::net::TcpListener, - (ssl_cert_path, ssl_key_path): (String, String), - tracker: Arc, - shutdown_signal: F, - ) -> BoxFuture<'static, ()> - where - F: Future + Send + 'static, - { - let app = router(tracker); - - let handle = Handle::new(); - - let cloned_handle = handle.clone(); - - tokio::task::spawn(async move { - shutdown_signal.await; - cloned_handle.shutdown(); - }); - - Box::pin(async { - let tls_config = RustlsConfig::from_pem_file(ssl_cert_path, ssl_key_path) - .await - .expect("Could not read tls cert."); - - axum_server::from_tcp_rustls(tcp_listener, tls_config) - .handle(handle) - .serve(app.into_make_service_with_connect_info::()) - .await - .expect("Axum server crashed."); - }) - } -} - -#[async_trait] -impl HttpServerLauncher for Launcher { - fn new() -> Self { - Self {} - } - - fn start_with_graceful_shutdown( - &self, - cfg: torrust_tracker_configuration::HttpTracker, - tracker: Arc, - shutdown_signal: F, - ) -> (SocketAddr, BoxFuture<'static, ()>) - where - F: Future + Send + 'static, - { - let addr = SocketAddr::from_str(&cfg.bind_address).expect("bind_address is not a valid SocketAddr."); - let tcp_listener = std::net::TcpListener::bind(addr).expect("Could not bind tcp_listener to address."); - let bind_addr = tcp_listener - .local_addr() - .expect("Could not get local_addr from tcp_listener."); - - if let (true, Some(ssl_cert_path), Some(ssl_key_path)) = (cfg.ssl_enabled, &cfg.ssl_cert_path, &cfg.ssl_key_path) { - let server = Self::start_tls_from_tcp_listener_with_graceful_shutdown( - tcp_listener, - (ssl_cert_path.to_string(), ssl_key_path.to_string()), - tracker, - shutdown_signal, - ); - - (bind_addr, server) - } else { - let server = Self::start_from_tcp_listener_with_graceful_shutdown(tcp_listener, tracker, shutdown_signal); - - (bind_addr, server) - } - } -} - -/// Starts a new HTTP server instance. -/// -/// # Panics -/// -/// Panics if the server could not listen to shutdown (ctrl+c) signal. -pub fn start(socket_addr: std::net::SocketAddr, tracker: Arc) -> impl Future> { - let app = router(tracker); - - let handle = Handle::new(); - - let cloned_handle = handle.clone(); - - tokio::task::spawn(async move { - tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal."); - info!("Stopping Torrust Health Check API server o http://{} ...", socket_addr); - cloned_handle.shutdown(); - }); - - axum_server::bind(socket_addr) - .handle(handle) - .serve(app.into_make_service_with_connect_info::()) -} - -/// Starts a new HTTPS server instance. -/// -/// # Panics -/// -/// Panics if the server could not listen to shutdown (ctrl+c) signal. -pub fn start_tls( - socket_addr: std::net::SocketAddr, - ssl_config: RustlsConfig, - tracker: Arc, -) -> impl Future> { - let app = router(tracker); - - let handle = Handle::new(); - let shutdown_handle = handle.clone(); - - tokio::spawn(async move { - tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal."); - info!("Stopping Torrust HTTP tracker server on https://{} ...", socket_addr); - shutdown_handle.shutdown(); - }); - - axum_server::bind_rustls(socket_addr, ssl_config) - .handle(handle) - .serve(app.into_make_service_with_connect_info::()) -} diff --git a/src/servers/http/v1/mod.rs b/src/servers/http/v1/mod.rs index 464a7ee14..9d2745692 100644 --- a/src/servers/http/v1/mod.rs +++ b/src/servers/http/v1/mod.rs @@ -4,7 +4,6 @@ //! more information about the endpoints and their usage. pub mod extractors; pub mod handlers; -pub mod launcher; pub mod query; pub mod requests; pub mod responses; diff --git a/src/servers/signals.rs b/src/servers/signals.rs index 51f53738d..cb0675d65 100644 --- a/src/servers/signals.rs +++ b/src/servers/signals.rs @@ -1,5 +1,17 @@ //! This module contains functions to handle signals. +use std::time::Duration; + +use derive_more::Display; use log::info; +use tokio::time::sleep; + +/// This is the message that the "launcher" spawned task receives from the main +/// application process to notify the service to shutdown. +/// +#[derive(Copy, Clone, Debug, Display)] +pub enum Halted { + Normal, +} /// Resolves on `ctrl_c` or the `terminate` signal. /// @@ -33,18 +45,33 @@ pub async fn global_shutdown_signal() { /// # Panics /// /// Will panic if the `stop_receiver` resolves with an error. -pub async fn shutdown_signal(stop_receiver: tokio::sync::oneshot::Receiver) { - let stop = async { stop_receiver.await.expect("Failed to install stop signal.") }; +pub async fn shutdown_signal(rx_halt: tokio::sync::oneshot::Receiver) { + let halt = async { rx_halt.await.expect("Failed to install stop signal.") }; tokio::select! { - _ = stop => {}, + _ = halt => {}, () = global_shutdown_signal() => {} } } /// Same as `shutdown_signal()`, but shows a message when it resolves. -pub async fn shutdown_signal_with_message(stop_receiver: tokio::sync::oneshot::Receiver, message: String) { - shutdown_signal(stop_receiver).await; +pub async fn shutdown_signal_with_message(rx_halt: tokio::sync::oneshot::Receiver, message: String) { + shutdown_signal(rx_halt).await; info!("{message}"); } + +pub async fn graceful_shutdown(handle: axum_server::Handle, rx_halt: tokio::sync::oneshot::Receiver, message: String) { + shutdown_signal_with_message(rx_halt, message).await; + + info!("sending graceful shutdown signal"); + handle.graceful_shutdown(Some(Duration::from_secs(90))); + + println!("!! shuting down in 90 seconds !!"); + + loop { + sleep(Duration::from_secs(1)).await; + + info!("remaining alive connections: {}", handle.connection_count()); + } +} diff --git a/src/servers/udp/handlers.rs b/src/servers/udp/handlers.rs index f3c7b58b0..18a341418 100644 --- a/src/servers/udp/handlers.rs +++ b/src/servers/udp/handlers.rs @@ -8,6 +8,7 @@ use aquatic_udp_protocol::{ NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId, }; use log::{debug, info}; +use torrust_tracker_located_error::DynError; use super::connection_cookie::{check, from_connection_id, into_connection_id, make}; use crate::core::{statistics, ScrapeData, Tracker}; @@ -46,7 +47,7 @@ pub async fn handle_packet(remote_addr: SocketAddr, payload: Vec, tracker: & // bad request Err(e) => handle_error( &Error::BadRequest { - source: (Arc::new(e) as Arc).into(), + source: (Arc::new(e) as DynError).into(), }, TransactionId(0), ), diff --git a/src/servers/udp/server.rs b/src/servers/udp/server.rs index 9b9a89b11..73a0c6655 100644 --- a/src/servers/udp/server.rs +++ b/src/servers/udp/server.rs @@ -17,19 +17,21 @@ //! because we want to be able to start and stop the server multiple times, and //! we want to know the bound address and the current state of the server. //! In production, the `Udp` launcher is used directly. -use std::future::Future; use std::io::Cursor; use std::net::SocketAddr; use std::sync::Arc; use aquatic_udp_protocol::Response; +use derive_more::Constructor; use futures::pin_mut; use log::{debug, error, info}; use tokio::net::UdpSocket; +use tokio::sync::oneshot::{Receiver, Sender}; use tokio::task::JoinHandle; +use crate::bootstrap::jobs::Started; use crate::core::Tracker; -use crate::servers::signals::shutdown_signal; +use crate::servers::signals::{shutdown_signal_with_message, Halted}; use crate::servers::udp::handlers::handle_packet; use crate::shared::bit_torrent::udp::MAX_PACKET_SIZE; @@ -75,29 +77,32 @@ pub type RunningUdpServer = UdpServer; /// intended to persist configurations between runs. #[allow(clippy::module_name_repetitions)] pub struct UdpServer { - /// The configuration of the server that will be used every time the server - /// is started. - pub cfg: torrust_tracker_configuration::UdpTracker, /// The state of the server: `running` or `stopped`. pub state: S, } /// A stopped UDP server state. -pub struct Stopped; + +pub struct Stopped { + launcher: Launcher, +} /// A running UDP server state. +#[derive(Debug, Constructor)] pub struct Running { /// The address where the server is bound. - pub bind_address: SocketAddr, - stop_job_sender: tokio::sync::oneshot::Sender, - job: JoinHandle<()>, + pub binding: SocketAddr, + pub halt_task: tokio::sync::oneshot::Sender, + pub task: JoinHandle, } impl UdpServer { /// Creates a new `UdpServer` instance in `stopped`state. #[must_use] - pub fn new(cfg: torrust_tracker_configuration::UdpTracker) -> Self { - Self { cfg, state: Stopped {} } + pub fn new(launcher: Launcher) -> Self { + Self { + state: Stopped { launcher }, + } } /// It starts the server and returns a `UdpServer` controller in `running` @@ -106,28 +111,32 @@ impl UdpServer { /// # Errors /// /// Will return `Err` if UDP can't bind to given bind address. + /// + /// # Panics + /// + /// It panics if unable to receive the bound socket address from service. + /// pub async fn start(self, tracker: Arc) -> Result, Error> { - let udp = Udp::new(&self.cfg.bind_address) - .await - .map_err(|e| Error::Error(e.to_string()))?; - - let bind_address = udp.socket.local_addr().map_err(|e| Error::Error(e.to_string()))?; + let (tx_start, rx_start) = tokio::sync::oneshot::channel::(); + let (tx_halt, rx_halt) = tokio::sync::oneshot::channel::(); - let (sender, receiver) = tokio::sync::oneshot::channel::(); + let launcher = self.state.launcher; - let job = tokio::spawn(async move { - udp.start_with_graceful_shutdown(tracker, shutdown_signal(receiver)).await; + let task = tokio::spawn(async move { + launcher.start(tracker, tx_start, rx_halt).await; + launcher }); let running_udp_server: UdpServer = UdpServer { - cfg: self.cfg, state: Running { - bind_address, - stop_job_sender: sender, - job, + binding: rx_start.await.expect("unable to start service").address, + halt_task: tx_halt, + task, }, }; + info!("Running UDP Tracker on Socket: {}", running_udp_server.state.binding); + Ok(running_udp_server) } } @@ -140,103 +149,96 @@ impl UdpServer { /// /// Will return `Err` if the oneshot channel to send the stop signal /// has already been called once. + /// + /// # Panics + /// + /// It panics if unable to shutdown service. pub async fn stop(self) -> Result, Error> { - self.state.stop_job_sender.send(1).map_err(|e| Error::Error(e.to_string()))?; + self.state + .halt_task + .send(Halted::Normal) + .map_err(|e| Error::Error(e.to_string()))?; - drop(self.state.job.await); + let launcher = self.state.task.await.expect("unable to shutdown service"); let stopped_api_server: UdpServer = UdpServer { - cfg: self.cfg, - state: Stopped {}, + state: Stopped { launcher }, }; Ok(stopped_api_server) } } -/// A UDP server instance launcher. -pub struct Udp { - socket: Arc, +#[derive(Constructor, Debug)] +pub struct Launcher { + bind_to: SocketAddr, } -impl Udp { - /// Creates a new `Udp` instance. - /// - /// # Errors - /// - /// Will return `Err` unable to bind to the supplied `bind_address`. - pub async fn new(bind_address: &str) -> tokio::io::Result { - let socket = UdpSocket::bind(bind_address).await?; - - Ok(Udp { - socket: Arc::new(socket), - }) - } - +impl Launcher { /// It starts the UDP server instance. /// /// # Panics /// /// It would panic if unable to resolve the `local_addr` from the supplied ´socket´. - pub async fn start(&self, tracker: Arc) { - loop { - let mut data = [0; MAX_PACKET_SIZE]; - let socket = self.socket.clone(); - - tokio::select! { - _ = tokio::signal::ctrl_c() => { - info!("Stopping UDP server: {}..", socket.local_addr().unwrap()); - break; - } - Ok((valid_bytes, remote_addr)) = socket.recv_from(&mut data) => { - let payload = data[..valid_bytes].to_vec(); - - debug!("Received {} bytes", payload.len()); - debug!("From: {}", &remote_addr); - debug!("Payload: {:?}", payload); - - let response = handle_packet(remote_addr, payload, &tracker).await; - - Udp::send_response(socket, remote_addr, response).await; - } - } - } + pub async fn start(&self, tracker: Arc, tx_start: Sender, rx_halt: Receiver) -> JoinHandle<()> { + Udp::start_with_graceful_shutdown(tracker, self.bind_to, tx_start, rx_halt).await } +} + +/// A UDP server instance launcher. +#[derive(Constructor)] +pub struct Udp; +impl Udp { /// It starts the UDP server instance with graceful shutdown. /// /// # Panics /// - /// It would panic if unable to resolve the `local_addr` from the supplied ´socket´. - async fn start_with_graceful_shutdown(&self, tracker: Arc, shutdown_signal: F) - where - F: Future, - { - // Pin the future so that it doesn't move to the first loop iteration. - pin_mut!(shutdown_signal); - - loop { - let mut data = [0; MAX_PACKET_SIZE]; - let socket = self.socket.clone(); - - tokio::select! { - () = &mut shutdown_signal => { - info!("Stopping UDP server: {}..", self.socket.local_addr().unwrap()); - break; + /// It panics if unable to bind to udp socket, and get the address from the udp socket. + /// It also panics if unable to send address of socket. + async fn start_with_graceful_shutdown( + tracker: Arc, + bind_to: SocketAddr, + tx_start: Sender, + rx_halt: Receiver, + ) -> JoinHandle<()> { + let binding = Arc::new(UdpSocket::bind(bind_to).await.expect("Could not bind to {self.socket}.")); + let address = binding.local_addr().expect("Could not get local_addr from {binding}."); + + let running = tokio::task::spawn(async move { + let halt = async move { + shutdown_signal_with_message(rx_halt, format!("Halting Http Service Bound to Socket: {address}")).await; + }; + + pin_mut!(halt); + + loop { + let mut data = [0; MAX_PACKET_SIZE]; + let binding = binding.clone(); + + tokio::select! { + () = & mut halt => {}, + + Ok((valid_bytes, remote_addr)) = binding.recv_from(&mut data) => { + let payload = data[..valid_bytes].to_vec(); + + debug!("Received {} bytes", payload.len()); + debug!("From: {}", &remote_addr); + debug!("Payload: {:?}", payload); + + let response = handle_packet(remote_addr, payload, &tracker).await; + + Udp::send_response(binding, remote_addr, response).await; + } } - Ok((valid_bytes, remote_addr)) = socket.recv_from(&mut data) => { - let payload = data[..valid_bytes].to_vec(); - - debug!("Received {} bytes", payload.len()); - debug!("From: {}", &remote_addr); - debug!("Payload: {:?}", payload); + } + }); - let response = handle_packet(remote_addr, payload, &tracker).await; + tx_start + .send(Started { address }) + .expect("the UDP Tracker service should not be dropped"); - Udp::send_response(socket, remote_addr, response).await; - } - } - } + running } async fn send_response(socket: Arc, remote_addr: SocketAddr, response: Response) { @@ -268,3 +270,31 @@ impl Udp { drop(socket.send_to(payload, remote_addr).await); } } + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use torrust_tracker_test_helpers::configuration::ephemeral_mode_public; + + use crate::bootstrap::app::initialize_with_configuration; + use crate::servers::udp::server::{Launcher, UdpServer}; + + #[tokio::test] + async fn it_should_be_able_to_start_and_stop() { + let cfg = Arc::new(ephemeral_mode_public()); + let tracker = initialize_with_configuration(&cfg); + let config = &cfg.http_trackers[0]; + + let bind_to = config + .bind_address + .parse::() + .expect("Tracker API bind_address invalid."); + + let stopped = UdpServer::new(Launcher::new(bind_to)); + let started = stopped.start(tracker).await.expect("it should start the server"); + let stopped = started.stop().await.expect("it should stop the server"); + + assert_eq!(stopped.state.launcher.bind_to, bind_to); + } +} diff --git a/tests/servers/api/test_environment.rs b/tests/servers/api/test_environment.rs index 0501d9c56..166bfd7d1 100644 --- a/tests/servers/api/test_environment.rs +++ b/tests/servers/api/test_environment.rs @@ -1,8 +1,12 @@ +use std::net::SocketAddr; use std::sync::Arc; +use axum_server::tls_rustls::RustlsConfig; +use futures::executor::block_on; +use torrust_tracker::bootstrap::jobs::make_rust_tls; use torrust_tracker::core::peer::Peer; use torrust_tracker::core::Tracker; -use torrust_tracker::servers::apis::server::{ApiServer, RunningApiServer, StoppedApiServer}; +use torrust_tracker::servers::apis::server::{ApiServer, Launcher, RunningApiServer, StoppedApiServer}; use torrust_tracker::shared::bit_torrent::info_hash::InfoHash; use super::connection_info::ConnectionInfo; @@ -36,15 +40,27 @@ impl TestEnvironment { } impl TestEnvironment { - pub fn new_stopped(cfg: torrust_tracker_configuration::Configuration) -> Self { - let cfg = Arc::new(cfg); + pub fn new(cfg: torrust_tracker_configuration::Configuration) -> Self { + let tracker = setup_with_configuration(&Arc::new(cfg)); - let tracker = setup_with_configuration(&cfg); + let config = tracker.config.http_api.clone(); - let api_server = api_server(cfg.http_api.clone()); + let bind_to = config + .bind_address + .parse::() + .expect("Tracker API bind_address invalid."); + + let tls = block_on(make_rust_tls(config.ssl_enabled, &config.ssl_cert_path, &config.ssl_key_path)) + .map(|tls| tls.expect("tls config failed")); + + Self::new_stopped(tracker, bind_to, tls) + } + + pub fn new_stopped(tracker: Arc, bind_to: SocketAddr, tls: Option) -> Self { + let api_server = api_server(Launcher::new(bind_to, tls)); Self { - cfg, + cfg: tracker.config.clone(), tracker, state: Stopped { api_server }, } @@ -60,14 +76,14 @@ impl TestEnvironment { } } - pub fn config_mut(&mut self) -> &mut torrust_tracker_configuration::HttpApi { - &mut self.state.api_server.cfg - } + // pub fn config_mut(&mut self) -> &mut torrust_tracker_configuration::HttpApi { + // &mut self.cfg.http_api + // } } impl TestEnvironment { pub async fn new_running(cfg: torrust_tracker_configuration::Configuration) -> Self { - let test_env = StoppedTestEnvironment::new_stopped(cfg); + let test_env = StoppedTestEnvironment::new(cfg); test_env.start().await } @@ -84,15 +100,16 @@ impl TestEnvironment { pub fn get_connection_info(&self) -> ConnectionInfo { ConnectionInfo { - bind_address: self.state.api_server.state.bind_addr.to_string(), - api_token: self.state.api_server.cfg.access_tokens.get("admin").cloned(), + bind_address: self.state.api_server.state.binding.to_string(), + api_token: self.cfg.http_api.access_tokens.get("admin").cloned(), } } } #[allow(clippy::module_name_repetitions)] +#[allow(dead_code)] pub fn stopped_test_environment(cfg: torrust_tracker_configuration::Configuration) -> StoppedTestEnvironment { - TestEnvironment::new_stopped(cfg) + TestEnvironment::new(cfg) } #[allow(clippy::module_name_repetitions)] @@ -100,6 +117,6 @@ pub async fn running_test_environment(cfg: torrust_tracker_configuration::Config TestEnvironment::new_running(cfg).await } -pub fn api_server(cfg: torrust_tracker_configuration::HttpApi) -> StoppedApiServer { - ApiServer::new(cfg) +pub fn api_server(launcher: Launcher) -> StoppedApiServer { + ApiServer::new(launcher) } diff --git a/tests/servers/api/v1/contract/configuration.rs b/tests/servers/api/v1/contract/configuration.rs index cfdb59b0c..a551a8b36 100644 --- a/tests/servers/api/v1/contract/configuration.rs +++ b/tests/servers/api/v1/contract/configuration.rs @@ -1,18 +1,33 @@ -use torrust_tracker_test_helpers::configuration; +// use std::sync::Arc; -use crate::servers::api::test_environment::stopped_test_environment; +// use axum_server::tls_rustls::RustlsConfig; +// use futures::executor::block_on; +// use torrust_tracker_test_helpers::configuration; + +// use crate::common::app::setup_with_configuration; +// use crate::servers::api::test_environment::stopped_test_environment; #[tokio::test] #[ignore] #[should_panic = "Could not receive bind_address."] async fn should_fail_with_ssl_enabled_and_bad_ssl_config() { - let mut test_env = stopped_test_environment(configuration::ephemeral()); + // let tracker = setup_with_configuration(&Arc::new(configuration::ephemeral())); + + // let config = tracker.config.http_api.clone(); + + // let bind_to = config + // .bind_address + // .parse::() + // .expect("Tracker API bind_address invalid."); - let cfg = test_env.config_mut(); + // let tls = + // if let (true, Some(cert), Some(key)) = (&true, &Some("bad cert path".to_string()), &Some("bad cert path".to_string())) { + // Some(block_on(RustlsConfig::from_pem_file(cert, key)).expect("Could not read tls cert.")) + // } else { + // None + // }; - cfg.ssl_enabled = true; - cfg.ssl_key_path = Some("bad key path".to_string()); - cfg.ssl_cert_path = Some("bad cert path".to_string()); + // let test_env = new_stopped(tracker, bind_to, tls); - test_env.start().await; + // test_env.start().await; } diff --git a/tests/servers/health_check_api/test_environment.rs b/tests/servers/health_check_api/test_environment.rs index 46e54dc47..554e37dbf 100644 --- a/tests/servers/health_check_api/test_environment.rs +++ b/tests/servers/health_check_api/test_environment.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use tokio::sync::oneshot; use tokio::task::JoinHandle; -use torrust_tracker::bootstrap::jobs::health_check_api::ApiServerJobStarted; +use torrust_tracker::bootstrap::jobs::Started; use torrust_tracker::servers::health_check_api::server; use torrust_tracker_configuration::Configuration; @@ -16,7 +16,7 @@ pub async fn start(config: Arc) -> (SocketAddr, JoinHandle<()>) { .parse::() .expect("Health Check API bind_address invalid."); - let (tx, rx) = oneshot::channel::(); + let (tx, rx) = oneshot::channel::(); let join_handle = tokio::spawn(async move { let handle = server::start(bind_addr, tx, config.clone()); @@ -26,7 +26,7 @@ pub async fn start(config: Arc) -> (SocketAddr, JoinHandle<()>) { }); let bound_addr = match rx.await { - Ok(msg) => msg.bound_addr, + Ok(msg) => msg.address, Err(e) => panic!("the Health Check API server was dropped: {e}"), }; diff --git a/tests/servers/http/test_environment.rs b/tests/servers/http/test_environment.rs index e24e1b9a5..73961b790 100644 --- a/tests/servers/http/test_environment.rs +++ b/tests/servers/http/test_environment.rs @@ -1,16 +1,18 @@ use std::sync::Arc; +use futures::executor::block_on; +use torrust_tracker::bootstrap::jobs::make_rust_tls; use torrust_tracker::core::peer::Peer; use torrust_tracker::core::Tracker; -use torrust_tracker::servers::http::server::{HttpServer, HttpServerLauncher, RunningHttpServer, StoppedHttpServer}; +use torrust_tracker::servers::http::server::{HttpServer, Launcher, RunningHttpServer, StoppedHttpServer}; use torrust_tracker::shared::bit_torrent::info_hash::InfoHash; use crate::common::app::setup_with_configuration; #[allow(clippy::module_name_repetitions, dead_code)] -pub type StoppedTestEnvironment = TestEnvironment>; +pub type StoppedTestEnvironment = TestEnvironment; #[allow(clippy::module_name_repetitions)] -pub type RunningTestEnvironment = TestEnvironment>; +pub type RunningTestEnvironment = TestEnvironment; pub struct TestEnvironment { pub cfg: Arc, @@ -19,12 +21,12 @@ pub struct TestEnvironment { } #[allow(dead_code)] -pub struct Stopped { - http_server: StoppedHttpServer, +pub struct Stopped { + http_server: StoppedHttpServer, } -pub struct Running { - http_server: RunningHttpServer, +pub struct Running { + http_server: RunningHttpServer, } impl TestEnvironment { @@ -34,14 +36,24 @@ impl TestEnvironment { } } -impl TestEnvironment> { +impl TestEnvironment { #[allow(dead_code)] pub fn new_stopped(cfg: torrust_tracker_configuration::Configuration) -> Self { let cfg = Arc::new(cfg); let tracker = setup_with_configuration(&cfg); - let http_server = http_server(cfg.http_trackers[0].clone()); + let config = cfg.http_trackers[0].clone(); + + let bind_to = config + .bind_address + .parse::() + .expect("Tracker API bind_address invalid."); + + let tls = block_on(make_rust_tls(config.ssl_enabled, &config.ssl_cert_path, &config.ssl_key_path)) + .map(|tls| tls.expect("tls config failed")); + + let http_server = HttpServer::new(Launcher::new(bind_to, tls)); Self { cfg, @@ -51,7 +63,7 @@ impl TestEnvironment> { } #[allow(dead_code)] - pub async fn start(self) -> TestEnvironment> { + pub async fn start(self) -> TestEnvironment { TestEnvironment { cfg: self.cfg, tracker: self.tracker.clone(), @@ -61,25 +73,25 @@ impl TestEnvironment> { } } - #[allow(dead_code)] - pub fn config(&self) -> &torrust_tracker_configuration::HttpTracker { - &self.state.http_server.cfg - } + // #[allow(dead_code)] + // pub fn config(&self) -> &torrust_tracker_configuration::HttpTracker { + // &self.state.http_server.cfg + // } - #[allow(dead_code)] - pub fn config_mut(&mut self) -> &mut torrust_tracker_configuration::HttpTracker { - &mut self.state.http_server.cfg - } + // #[allow(dead_code)] + // pub fn config_mut(&mut self) -> &mut torrust_tracker_configuration::HttpTracker { + // &mut self.state.http_server.cfg + // } } -impl TestEnvironment> { +impl TestEnvironment { pub async fn new_running(cfg: torrust_tracker_configuration::Configuration) -> Self { let test_env = StoppedTestEnvironment::new_stopped(cfg); test_env.start().await } - pub async fn stop(self) -> TestEnvironment> { + pub async fn stop(self) -> TestEnvironment { TestEnvironment { cfg: self.cfg, tracker: self.tracker, @@ -90,31 +102,26 @@ impl TestEnvironment> { } pub fn bind_address(&self) -> &std::net::SocketAddr { - &self.state.http_server.state.bind_addr + &self.state.http_server.state.binding } - #[allow(dead_code)] - pub fn config(&self) -> &torrust_tracker_configuration::HttpTracker { - &self.state.http_server.cfg - } + // #[allow(dead_code)] + // pub fn config(&self) -> &torrust_tracker_configuration::HttpTracker { + // &self.state.http_server.cfg + // } } #[allow(clippy::module_name_repetitions, dead_code)] -pub fn stopped_test_environment( - cfg: torrust_tracker_configuration::Configuration, -) -> StoppedTestEnvironment { +pub fn stopped_test_environment(cfg: torrust_tracker_configuration::Configuration) -> StoppedTestEnvironment { TestEnvironment::new_stopped(cfg) } #[allow(clippy::module_name_repetitions)] -pub async fn running_test_environment( - cfg: torrust_tracker_configuration::Configuration, -) -> RunningTestEnvironment { +pub async fn running_test_environment(cfg: torrust_tracker_configuration::Configuration) -> RunningTestEnvironment { TestEnvironment::new_running(cfg).await } -pub fn http_server(cfg: torrust_tracker_configuration::HttpTracker) -> StoppedHttpServer { - let http_server = I::new(); - - HttpServer::new(cfg, http_server) +#[allow(dead_code)] +pub fn http_server(launcher: Launcher) -> StoppedHttpServer { + HttpServer::new(launcher) } diff --git a/tests/servers/http/v1/contract.rs b/tests/servers/http/v1/contract.rs index 9a6aa2454..3034847db 100644 --- a/tests/servers/http/v1/contract.rs +++ b/tests/servers/http/v1/contract.rs @@ -2,11 +2,9 @@ use torrust_tracker_test_helpers::configuration; use crate::servers::http::test_environment::running_test_environment; -pub type V1 = torrust_tracker::servers::http::v1::launcher::Launcher; - #[tokio::test] async fn test_environment_should_be_started_and_stopped() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; test_env.stop().await; } @@ -18,11 +16,10 @@ mod for_all_config_modes { use crate::servers::http::client::Client; use crate::servers::http::test_environment::running_test_environment; - use crate::servers::http::v1::contract::V1; #[tokio::test] async fn health_check_endpoint_should_return_ok_if_the_http_tracker_is_running() { - let test_env = running_test_environment::(configuration::ephemeral_with_reverse_proxy()).await; + let test_env = running_test_environment(configuration::ephemeral_with_reverse_proxy()).await; let response = Client::new(*test_env.bind_address()).health_check().await; @@ -40,14 +37,13 @@ mod for_all_config_modes { use crate::servers::http::client::Client; use crate::servers::http::requests::announce::QueryBuilder; use crate::servers::http::test_environment::running_test_environment; - use crate::servers::http::v1::contract::V1; #[tokio::test] async fn should_fail_when_the_http_request_does_not_include_the_xff_http_request_header() { // If the tracker is running behind a reverse proxy, the peer IP is the // right most IP in the `X-Forwarded-For` HTTP header, which is the IP of the proxy's client. - let test_env = running_test_environment::(configuration::ephemeral_with_reverse_proxy()).await; + let test_env = running_test_environment(configuration::ephemeral_with_reverse_proxy()).await; let params = QueryBuilder::default().query().params(); @@ -60,7 +56,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_the_xff_http_request_header_contains_an_invalid_ip() { - let test_env = running_test_environment::(configuration::ephemeral_with_reverse_proxy()).await; + let test_env = running_test_environment(configuration::ephemeral_with_reverse_proxy()).await; let params = QueryBuilder::default().query().params(); @@ -91,7 +87,7 @@ mod for_all_config_modes { use std::str::FromStr; use local_ip_address::local_ip; - use reqwest::Response; + use reqwest::{Response, StatusCode}; use tokio::net::TcpListener; use torrust_tracker::core::peer; use torrust_tracker::shared::bit_torrent::info_hash::InfoHash; @@ -108,11 +104,16 @@ mod for_all_config_modes { use crate::servers::http::responses; use crate::servers::http::responses::announce::{Announce, CompactPeer, CompactPeerList, DictionaryPeer}; use crate::servers::http::test_environment::running_test_environment; - use crate::servers::http::v1::contract::V1; + + #[tokio::test] + async fn it_should_start_and_stop() { + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; + test_env.stop().await; + } #[tokio::test] async fn should_respond_if_only_the_mandatory_fields_are_provided() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; let mut params = QueryBuilder::default().query().params(); @@ -127,7 +128,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_the_url_query_component_is_empty() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; let response = Client::new(*test_env.bind_address()).get("announce").await; @@ -138,7 +139,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_url_query_parameters_are_invalid() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; let invalid_query_param = "a=b=c"; @@ -153,7 +154,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_a_mandatory_field_is_missing() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; // Without `info_hash` param @@ -190,7 +191,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_the_info_hash_param_is_invalid() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; let mut params = QueryBuilder::default().query().params(); @@ -212,7 +213,7 @@ mod for_all_config_modes { // 1. If tracker is NOT running `on_reverse_proxy` from the remote client IP. // 2. If tracker is running `on_reverse_proxy` from `X-Forwarded-For` request HTTP header. - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; let mut params = QueryBuilder::default().query().params(); @@ -227,7 +228,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_the_downloaded_param_is_invalid() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; let mut params = QueryBuilder::default().query().params(); @@ -246,7 +247,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_the_uploaded_param_is_invalid() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; let mut params = QueryBuilder::default().query().params(); @@ -265,7 +266,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_the_peer_id_param_is_invalid() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; let mut params = QueryBuilder::default().query().params(); @@ -291,7 +292,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_the_port_param_is_invalid() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; let mut params = QueryBuilder::default().query().params(); @@ -310,7 +311,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_the_left_param_is_invalid() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; let mut params = QueryBuilder::default().query().params(); @@ -329,7 +330,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_the_event_param_is_invalid() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; let mut params = QueryBuilder::default().query().params(); @@ -356,7 +357,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_the_compact_param_is_invalid() { - let test_env = running_test_environment::(configuration::ephemeral()).await; + let test_env = running_test_environment(configuration::ephemeral()).await; let mut params = QueryBuilder::default().query().params(); @@ -375,7 +376,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_return_no_peers_if_the_announced_peer_is_the_first_one() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let response = Client::new(*test_env.bind_address()) .announce( @@ -402,7 +403,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_return_the_list_of_previously_announced_peers() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -442,7 +443,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_return_the_list_of_previously_announced_peers_including_peers_using_ipv4_and_ipv6() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -492,7 +493,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_consider_two_peers_to_be_the_same_when_they_have_the_same_peer_id_even_if_the_ip_is_different() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); let peer = PeerBuilder::default().build(); @@ -519,7 +520,7 @@ mod for_all_config_modes { // Tracker Returns Compact Peer Lists // https://www.bittorrent.org/beps/bep_0023.html - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -560,7 +561,7 @@ mod for_all_config_modes { // code-review: the HTTP tracker does not return the compact response by default if the "compact" // param is not provided in the announce URL. The BEP 23 suggest to do so. - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -598,7 +599,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_increase_the_number_of_tcp4_connections_handled_in_statistics() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; Client::new(*test_env.bind_address()) .announce(&QueryBuilder::default().query()) @@ -622,7 +623,7 @@ mod for_all_config_modes { return; // we cannot bind to a ipv6 socket, so we will skip this test } - let test_env = running_test_environment::(configuration::ephemeral_ipv6()).await; + let test_env = running_test_environment(configuration::ephemeral_ipv6()).await; Client::bind(*test_env.bind_address(), IpAddr::from_str("::1").unwrap()) .announce(&QueryBuilder::default().query()) @@ -641,7 +642,7 @@ mod for_all_config_modes { async fn should_not_increase_the_number_of_tcp6_connections_handled_if_the_client_is_not_using_an_ipv6_ip() { // The tracker ignores the peer address in the request param. It uses the client remote ip address. - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; Client::new(*test_env.bind_address()) .announce( @@ -662,7 +663,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_increase_the_number_of_tcp4_announce_requests_handled_in_statistics() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; Client::new(*test_env.bind_address()) .announce(&QueryBuilder::default().query()) @@ -686,7 +687,7 @@ mod for_all_config_modes { return; // we cannot bind to a ipv6 socket, so we will skip this test } - let test_env = running_test_environment::(configuration::ephemeral_ipv6()).await; + let test_env = running_test_environment(configuration::ephemeral_ipv6()).await; Client::bind(*test_env.bind_address(), IpAddr::from_str("::1").unwrap()) .announce(&QueryBuilder::default().query()) @@ -705,7 +706,7 @@ mod for_all_config_modes { async fn should_not_increase_the_number_of_tcp6_announce_requests_handled_if_the_client_is_not_using_an_ipv6_ip() { // The tracker ignores the peer address in the request param. It uses the client remote ip address. - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; Client::new(*test_env.bind_address()) .announce( @@ -726,19 +727,22 @@ mod for_all_config_modes { #[tokio::test] async fn should_assign_to_the_peer_ip_the_remote_client_ip_instead_of_the_peer_address_in_the_request_param() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); let client_ip = local_ip().unwrap(); - let client = Client::bind(*test_env.bind_address(), client_ip); - let announce_query = QueryBuilder::default() .with_info_hash(&info_hash) .with_peer_addr(&IpAddr::from_str("2.2.2.2").unwrap()) .query(); - client.announce(&announce_query).await; + { + let client = Client::bind(*test_env.bind_address(), client_ip); + let status = client.announce(&announce_query).await.status(); + + assert_eq!(status, StatusCode::OK); + } let peers = test_env.tracker.get_torrent_peers(&info_hash).await; let peer_addr = peers[0].peer_addr; @@ -758,7 +762,7 @@ mod for_all_config_modes { 127.0.0.1 external_ip = "2.137.87.41" */ - let test_env = running_test_environment::(configuration::ephemeral_with_external_ip( + let test_env = running_test_environment(configuration::ephemeral_with_external_ip( IpAddr::from_str("2.137.87.41").unwrap(), )) .await; @@ -767,14 +771,17 @@ mod for_all_config_modes { let loopback_ip = IpAddr::from_str("127.0.0.1").unwrap(); let client_ip = loopback_ip; - let client = Client::bind(*test_env.bind_address(), client_ip); - let announce_query = QueryBuilder::default() .with_info_hash(&info_hash) .with_peer_addr(&IpAddr::from_str("2.2.2.2").unwrap()) .query(); - client.announce(&announce_query).await; + { + let client = Client::bind(*test_env.bind_address(), client_ip); + let status = client.announce(&announce_query).await.status(); + + assert_eq!(status, StatusCode::OK); + } let peers = test_env.tracker.get_torrent_peers(&info_hash).await; let peer_addr = peers[0].peer_addr; @@ -794,7 +801,7 @@ mod for_all_config_modes { ::1 external_ip = "2345:0425:2CA1:0000:0000:0567:5673:23b5" */ - let test_env = running_test_environment::(configuration::ephemeral_with_external_ip( + let test_env = running_test_environment(configuration::ephemeral_with_external_ip( IpAddr::from_str("2345:0425:2CA1:0000:0000:0567:5673:23b5").unwrap(), )) .await; @@ -803,14 +810,17 @@ mod for_all_config_modes { let loopback_ip = IpAddr::from_str("127.0.0.1").unwrap(); let client_ip = loopback_ip; - let client = Client::bind(*test_env.bind_address(), client_ip); - let announce_query = QueryBuilder::default() .with_info_hash(&info_hash) .with_peer_addr(&IpAddr::from_str("2.2.2.2").unwrap()) .query(); - client.announce(&announce_query).await; + { + let client = Client::bind(*test_env.bind_address(), client_ip); + let status = client.announce(&announce_query).await.status(); + + assert_eq!(status, StatusCode::OK); + } let peers = test_env.tracker.get_torrent_peers(&info_hash).await; let peer_addr = peers[0].peer_addr; @@ -830,21 +840,25 @@ mod for_all_config_modes { 145.254.214.256 X-Forwarded-For = 145.254.214.256 on_reverse_proxy = true 145.254.214.256 */ - let test_env = running_test_environment::(configuration::ephemeral_with_reverse_proxy()).await; + let test_env = running_test_environment(configuration::ephemeral_with_reverse_proxy()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); - let client = Client::new(*test_env.bind_address()); - let announce_query = QueryBuilder::default().with_info_hash(&info_hash).query(); - client - .announce_with_header( - &announce_query, - "X-Forwarded-For", - "203.0.113.195,2001:db8:85a3:8d3:1319:8a2e:370:7348,150.172.238.178", - ) - .await; + { + let client = Client::new(*test_env.bind_address()); + let status = client + .announce_with_header( + &announce_query, + "X-Forwarded-For", + "203.0.113.195,2001:db8:85a3:8d3:1319:8a2e:370:7348,150.172.238.178", + ) + .await + .status(); + + assert_eq!(status, StatusCode::OK); + } let peers = test_env.tracker.get_torrent_peers(&info_hash).await; let peer_addr = peers[0].peer_addr; @@ -883,12 +897,11 @@ mod for_all_config_modes { use crate::servers::http::requests::scrape::QueryBuilder; use crate::servers::http::responses::scrape::{self, File, ResponseBuilder}; use crate::servers::http::test_environment::running_test_environment; - use crate::servers::http::v1::contract::V1; //#[tokio::test] #[allow(dead_code)] async fn should_fail_when_the_request_is_empty() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let response = Client::new(*test_env.bind_address()).get("scrape").await; assert_missing_query_params_for_scrape_request_error_response(response).await; @@ -898,7 +911,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_fail_when_the_info_hash_param_is_invalid() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let mut params = QueryBuilder::default().query().params(); @@ -915,7 +928,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_return_the_file_with_the_incomplete_peer_when_there_is_one_peer_with_bytes_pending_to_download() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -955,7 +968,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_return_the_file_with_the_complete_peer_when_there_is_one_peer_with_no_bytes_pending_to_download() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -995,7 +1008,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_return_a_file_with_zeroed_values_when_there_are_no_peers() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -1014,7 +1027,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_accept_multiple_infohashes() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let info_hash1 = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); let info_hash2 = InfoHash::from_str("3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0").unwrap(); @@ -1040,7 +1053,7 @@ mod for_all_config_modes { #[tokio::test] async fn should_increase_the_number_ot_tcp4_scrape_requests_handled_in_statistics() { - let test_env = running_test_environment::(configuration::ephemeral_mode_public()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_public()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -1070,7 +1083,7 @@ mod for_all_config_modes { return; // we cannot bind to a ipv6 socket, so we will skip this test } - let test_env = running_test_environment::(configuration::ephemeral_ipv6()).await; + let test_env = running_test_environment(configuration::ephemeral_ipv6()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -1105,11 +1118,10 @@ mod configured_as_whitelisted { use crate::servers::http::client::Client; use crate::servers::http::requests::announce::QueryBuilder; use crate::servers::http::test_environment::running_test_environment; - use crate::servers::http::v1::contract::V1; #[tokio::test] async fn should_fail_if_the_torrent_is_not_in_the_whitelist() { - let test_env = running_test_environment::(configuration::ephemeral_mode_whitelisted()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_whitelisted()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -1124,7 +1136,7 @@ mod configured_as_whitelisted { #[tokio::test] async fn should_allow_announcing_a_whitelisted_torrent() { - let test_env = running_test_environment::(configuration::ephemeral_mode_whitelisted()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_whitelisted()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -1157,11 +1169,10 @@ mod configured_as_whitelisted { use crate::servers::http::requests; use crate::servers::http::responses::scrape::{File, ResponseBuilder}; use crate::servers::http::test_environment::running_test_environment; - use crate::servers::http::v1::contract::V1; #[tokio::test] async fn should_return_the_zeroed_file_when_the_requested_file_is_not_whitelisted() { - let test_env = running_test_environment::(configuration::ephemeral_mode_whitelisted()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_whitelisted()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -1192,7 +1203,7 @@ mod configured_as_whitelisted { #[tokio::test] async fn should_return_the_file_stats_when_the_requested_file_is_whitelisted() { - let test_env = running_test_environment::(configuration::ephemeral_mode_whitelisted()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_whitelisted()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -1252,11 +1263,10 @@ mod configured_as_private { use crate::servers::http::client::Client; use crate::servers::http::requests::announce::QueryBuilder; use crate::servers::http::test_environment::running_test_environment; - use crate::servers::http::v1::contract::V1; #[tokio::test] async fn should_respond_to_authenticated_peers() { - let test_env = running_test_environment::(configuration::ephemeral_mode_private()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_private()).await; let expiring_key = test_env.tracker.generate_auth_key(Duration::from_secs(60)).await.unwrap(); @@ -1271,7 +1281,7 @@ mod configured_as_private { #[tokio::test] async fn should_fail_if_the_peer_has_not_provided_the_authentication_key() { - let test_env = running_test_environment::(configuration::ephemeral_mode_private()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_private()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -1286,7 +1296,7 @@ mod configured_as_private { #[tokio::test] async fn should_fail_if_the_key_query_param_cannot_be_parsed() { - let test_env = running_test_environment::(configuration::ephemeral_mode_private()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_private()).await; let invalid_key = "INVALID_KEY"; @@ -1301,7 +1311,7 @@ mod configured_as_private { #[tokio::test] async fn should_fail_if_the_peer_cannot_be_authenticated_with_the_provided_key() { - let test_env = running_test_environment::(configuration::ephemeral_mode_private()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_private()).await; // The tracker does not have this key let unregistered_key = Key::from_str("YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ").unwrap(); @@ -1332,11 +1342,10 @@ mod configured_as_private { use crate::servers::http::requests; use crate::servers::http::responses::scrape::{File, ResponseBuilder}; use crate::servers::http::test_environment::running_test_environment; - use crate::servers::http::v1::contract::V1; #[tokio::test] async fn should_fail_if_the_key_query_param_cannot_be_parsed() { - let test_env = running_test_environment::(configuration::ephemeral_mode_private()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_private()).await; let invalid_key = "INVALID_KEY"; @@ -1351,7 +1360,7 @@ mod configured_as_private { #[tokio::test] async fn should_return_the_zeroed_file_when_the_client_is_not_authenticated() { - let test_env = running_test_environment::(configuration::ephemeral_mode_private()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_private()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -1382,7 +1391,7 @@ mod configured_as_private { #[tokio::test] async fn should_return_the_real_file_stats_when_the_client_is_authenticated() { - let test_env = running_test_environment::(configuration::ephemeral_mode_private()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_private()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); @@ -1427,7 +1436,7 @@ mod configured_as_private { // There is not authentication error // code-review: should this really be this way? - let test_env = running_test_environment::(configuration::ephemeral_mode_private()).await; + let test_env = running_test_environment(configuration::ephemeral_mode_private()).await; let info_hash = InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap(); diff --git a/tests/servers/udp/test_environment.rs b/tests/servers/udp/test_environment.rs index dfe19ac86..bbad6d927 100644 --- a/tests/servers/udp/test_environment.rs +++ b/tests/servers/udp/test_environment.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use torrust_tracker::core::peer::Peer; use torrust_tracker::core::Tracker; -use torrust_tracker::servers::udp::server::{RunningUdpServer, StoppedUdpServer, UdpServer}; +use torrust_tracker::servers::udp::server::{Launcher, RunningUdpServer, StoppedUdpServer, UdpServer}; use torrust_tracker::shared::bit_torrent::info_hash::InfoHash; use crate::common::app::setup_with_configuration; @@ -43,7 +43,14 @@ impl TestEnvironment { let tracker = setup_with_configuration(&cfg); - let udp_server = udp_server(cfg.udp_trackers[0].clone()); + let udp_cfg = cfg.udp_trackers[0].clone(); + + let bind_to = udp_cfg + .bind_address + .parse::() + .expect("Tracker API bind_address invalid."); + + let udp_server = udp_server(Launcher::new(bind_to)); Self { cfg, @@ -81,7 +88,7 @@ impl TestEnvironment { } pub fn bind_address(&self) -> SocketAddr { - self.state.udp_server.state.bind_address + self.state.udp_server.state.binding } } @@ -95,6 +102,6 @@ pub async fn running_test_environment(cfg: torrust_tracker_configuration::Config TestEnvironment::new_running(cfg).await } -pub fn udp_server(cfg: torrust_tracker_configuration::UdpTracker) -> StoppedUdpServer { - UdpServer::new(cfg) +pub fn udp_server(launcher: Launcher) -> StoppedUdpServer { + UdpServer::new(launcher) }