From 85109900a9e8c6d943246e0cfd8fc32ddcf22d47 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 24 Mar 2025 12:15:47 +0000 Subject: [PATCH 1/6] test: [#1407] add test for global metrics with 2 http trackers A new integration test that checks that the global metrics are udapted when you run 2 HTTP trackers. Ony one metric is checked in this test. It uses fixed port that migth conflict with other running instances in the future. We should use a random free port if we run more integration tests like this in the future. --- Cargo.lock | 2 + Cargo.toml | 2 + src/app.rs | 2 + tests/servers/api/contract/mod.rs | 1 + tests/servers/api/contract/stats/mod.rs | 95 +++++++++++++++++++++++++ tests/servers/api/mod.rs | 1 + tests/servers/mod.rs | 1 + 7 files changed, 104 insertions(+) create mode 100644 tests/servers/api/contract/mod.rs create mode 100644 tests/servers/api/contract/stats/mod.rs create mode 100644 tests/servers/api/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 076449944..055c02a9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4521,6 +4521,8 @@ dependencies = [ "anyhow", "axum-server", "bittorrent-http-tracker-core", + "bittorrent-primitives", + "bittorrent-tracker-client", "bittorrent-tracker-core", "bittorrent-udp-tracker-core", "chrono", diff --git a/Cargo.toml b/Cargo.toml index bcac4bf66..91393ad72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,8 @@ tracing = "0" tracing-subscriber = { version = "0", features = ["json"] } [dev-dependencies] +bittorrent-primitives = "0.1.0" +bittorrent-tracker-client = { version = "3.0.0-develop", path = "packages/tracker-client" } local-ip-address = "0" mockall = "0" torrust-rest-tracker-api-client = { version = "3.0.0-develop", path = "packages/rest-tracker-api-client" } diff --git a/src/app.rs b/src/app.rs index fb8a459ea..fcd650c24 100644 --- a/src/app.rs +++ b/src/app.rs @@ -138,6 +138,8 @@ pub async fn start(config: &Configuration, app_container: &Arc) -> )); } + println!("Registar entries: {:?}", registar.entries()); + // Start Health Check API jobs.push(health_check_api::start_job(&config.health_check_api, registar.entries()).await); diff --git a/tests/servers/api/contract/mod.rs b/tests/servers/api/contract/mod.rs new file mode 100644 index 000000000..9d34677fc --- /dev/null +++ b/tests/servers/api/contract/mod.rs @@ -0,0 +1 @@ +pub mod stats; diff --git a/tests/servers/api/contract/stats/mod.rs b/tests/servers/api/contract/stats/mod.rs new file mode 100644 index 000000000..fa7b4e6aa --- /dev/null +++ b/tests/servers/api/contract/stats/mod.rs @@ -0,0 +1,95 @@ +use std::env; +use std::str::FromStr as _; +use std::sync::Arc; + +use bittorrent_primitives::info_hash::InfoHash; +use bittorrent_tracker_client::http::client::requests::announce::QueryBuilder; +use bittorrent_tracker_client::http::client::Client as HttpTrackerClient; +use reqwest::Url; +use serde::Deserialize; +use tokio::time::Duration; +use torrust_rest_tracker_api_client::connection_info::{ConnectionInfo, Origin}; +use torrust_rest_tracker_api_client::v1::client::Client as TrackerApiClient; +use torrust_tracker_lib::{app, bootstrap}; + +#[tokio::test] +async fn the_stats_api_endpoint_should_return_the_global_stats() { + // Logging must be OFF otherwise your will get the following error: + // `Unable to install global subscriber: SetGlobalDefaultError("a global default trace dispatcher has already been set")` + // That's because we can't initialize the logger twice. + // You can enable it if you run only this test. + let config_with_two_http_trackers = r#" + [metadata] + app = "torrust-tracker" + purpose = "configuration" + schema_version = "2.0.0" + + [logging] + threshold = "off" + + [core] + listed = false + private = false + + [[http_trackers]] + bind_address = "0.0.0.0:7272" + tracker_usage_statistics = true + + [[http_trackers]] + bind_address = "0.0.0.0:7373" + tracker_usage_statistics = true + + [http_api] + bind_address = "0.0.0.0:1414" + + [http_api.access_tokens] + admin = "MyAccessToken" + "#; + + env::set_var("TORRUST_TRACKER_CONFIG_TOML", config_with_two_http_trackers); + + let (config, app_container) = bootstrap::app::setup(); + + let app_container = Arc::new(app_container); + + let _jobs = app::start(&config, &app_container).await; + + announce_to_tracker("http://127.0.0.1:7272").await; + announce_to_tracker("http://127.0.0.1:7373").await; + + let partial_metrics = get_partial_metrics("http://127.0.0.1:1414", "MyAccessToken").await; + + assert_eq!(partial_metrics.tcp4_announces_handled, 2); +} + +/// Make a sample announce request to the tracker. +async fn announce_to_tracker(tracker_url: &str) { + let response = HttpTrackerClient::new(Url::parse(tracker_url).unwrap(), Duration::from_secs(1)) + .unwrap() + .announce( + &QueryBuilder::with_default_values() + .with_info_hash(&InfoHash::from_str("9c38422213e30bff212b30c360d26f9a02136422").unwrap()) // DevSkim: ignore DS173237 + .query(), + ) + .await; + + assert!(response.is_ok()); +} + +/// Metrics only relevant to the test. +#[derive(Deserialize)] +struct PartialMetrics { + tcp4_announces_handled: u64, +} + +async fn get_partial_metrics(aip_url: &str, token: &str) -> PartialMetrics { + let response = TrackerApiClient::new(ConnectionInfo::authenticated(Origin::new(aip_url).unwrap(), token)) + .unwrap() + .get_tracker_statistics(None) + .await; + + response + .json::() + .await + .expect("Failed to parse JSON response") +} diff --git a/tests/servers/api/mod.rs b/tests/servers/api/mod.rs new file mode 100644 index 000000000..2943dbb50 --- /dev/null +++ b/tests/servers/api/mod.rs @@ -0,0 +1 @@ +pub mod contract; diff --git a/tests/servers/mod.rs b/tests/servers/mod.rs index 7aeefeec4..0bbd5c433 100644 --- a/tests/servers/mod.rs +++ b/tests/servers/mod.rs @@ -1 +1,2 @@ +pub mod api; pub mod health_check_api; From eeea77a9c61d59ef90124639519b8c945b36d1e4 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 24 Mar 2025 12:24:26 +0000 Subject: [PATCH 2/6] chore: remove sample integration test now that we have a real one. --- tests/integration.rs | 7 ++++--- tests/servers/health_check_api.rs | 32 ------------------------------- tests/servers/mod.rs | 1 - 3 files changed, 4 insertions(+), 36 deletions(-) delete mode 100644 tests/servers/health_check_api.rs diff --git a/tests/integration.rs b/tests/integration.rs index 6a139e047..92289c415 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,13 +1,14 @@ //! Scaffolding for integration tests. //! +//! Integration tests are used to test the interaction between multiple modules, +//! multiple running trackers, etc. Tests for one specific module should be in +//! the corresponding package. +//! //! ```text //! cargo test --test integration //! ``` mod servers; -// todo: there is only one test example that was copied from other package. -// We have to add tests for the whole app. - use torrust_tracker_clock::clock; /// This code needs to be copied into each crate. diff --git a/tests/servers/health_check_api.rs b/tests/servers/health_check_api.rs deleted file mode 100644 index 0e66014da..000000000 --- a/tests/servers/health_check_api.rs +++ /dev/null @@ -1,32 +0,0 @@ -use reqwest::Response; -use torrust_axum_health_check_api_server::environment::Started; -use torrust_axum_health_check_api_server::resources::{Report, Status}; -use torrust_server_lib::registar::Registar; -use torrust_tracker_test_helpers::{configuration, logging}; - -pub async fn get(path: &str) -> Response { - reqwest::Client::builder().build().unwrap().get(path).send().await.unwrap() -} - -#[tokio::test] -async fn the_health_check_endpoint_should_return_status_ok_when_there_is_not_any_service_registered() { - logging::setup(); - - let configuration = configuration::ephemeral_with_no_services(); - - let env = Started::new(&configuration.health_check_api.into(), Registar::default()).await; - - let response = get(&format!("http://{}/health_check", env.state.binding)).await; // DevSkim: ignore DS137138 - - assert_eq!(response.status(), 200); - assert_eq!(response.headers().get("content-type").unwrap(), "application/json"); - - let report = response - .json::() - .await - .expect("it should be able to get the report as json"); - - assert_eq!(report.status, Status::None); - - env.stop().await.expect("it should stop the service"); -} diff --git a/tests/servers/mod.rs b/tests/servers/mod.rs index 0bbd5c433..e5fdf85ee 100644 --- a/tests/servers/mod.rs +++ b/tests/servers/mod.rs @@ -1,2 +1 @@ pub mod api; -pub mod health_check_api; From 398ad9bad7fa7af67ddfe8de5dbc356871ed51b4 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 24 Mar 2025 12:33:50 +0000 Subject: [PATCH 3/6] refactor: remove duplicate code --- src/app.rs | 15 +++++++++++++-- src/console/profiling.rs | 2 +- src/main.rs | 10 ++-------- tests/servers/api/contract/stats/mod.rs | 9 ++------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/app.rs b/src/app.rs index fcd650c24..007eb16d0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -28,9 +28,20 @@ use torrust_server_lib::registar::Registar; use torrust_tracker_configuration::Configuration; use tracing::instrument; +use crate::bootstrap; use crate::bootstrap::jobs::{health_check_api, http_tracker, torrent_cleanup, tracker_apis, udp_tracker}; use crate::container::AppContainer; +pub async fn run() -> (Arc, Vec>, Registar) { + let (config, app_container) = bootstrap::app::setup(); + + let app_container = Arc::new(app_container); + + let (jobs, registar) = start(&config, &app_container).await; + + (app_container, jobs, registar) +} + /// # Panics /// /// Will panic if: @@ -38,7 +49,7 @@ use crate::container::AppContainer; /// - Can't retrieve tracker keys from database. /// - Can't load whitelist from database. #[instrument(skip(config, app_container))] -pub async fn start(config: &Configuration, app_container: &Arc) -> Vec> { +pub async fn start(config: &Configuration, app_container: &Arc) -> (Vec>, Registar) { if config.http_api.is_none() && (config.udp_trackers.is_none() || config.udp_trackers.as_ref().map_or(true, std::vec::Vec::is_empty)) && (config.http_trackers.is_none() || config.http_trackers.as_ref().map_or(true, std::vec::Vec::is_empty)) @@ -143,5 +154,5 @@ pub async fn start(config: &Configuration, app_container: &Arc) -> // Start Health Check API jobs.push(health_check_api::start_job(&config.health_check_api, registar.entries()).await); - jobs + (jobs, registar) } diff --git a/src/console/profiling.rs b/src/console/profiling.rs index f3829c073..ffbd835fb 100644 --- a/src/console/profiling.rs +++ b/src/console/profiling.rs @@ -184,7 +184,7 @@ pub async fn run() { let app_container = Arc::new(app_container); - let jobs = app::start(&config, &app_container).await; + let (jobs, _registar) = app::start(&config, &app_container).await; // Run the tracker for a fixed duration let run_duration = sleep(Duration::from_secs(duration_secs)); diff --git a/src/main.rs b/src/main.rs index 77f6e32a3..cc7c202c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,8 @@ -use std::sync::Arc; - -use torrust_tracker_lib::{app, bootstrap}; +use torrust_tracker_lib::app; #[tokio::main] async fn main() { - let (config, app_container) = bootstrap::app::setup(); - - let app_container = Arc::new(app_container); - - let jobs = app::start(&config, &app_container).await; + let (_app_container, jobs, _registar) = app::run().await; // handle the signals tokio::select! { diff --git a/tests/servers/api/contract/stats/mod.rs b/tests/servers/api/contract/stats/mod.rs index fa7b4e6aa..c31ab1907 100644 --- a/tests/servers/api/contract/stats/mod.rs +++ b/tests/servers/api/contract/stats/mod.rs @@ -1,6 +1,5 @@ use std::env; use std::str::FromStr as _; -use std::sync::Arc; use bittorrent_primitives::info_hash::InfoHash; use bittorrent_tracker_client::http::client::requests::announce::QueryBuilder; @@ -10,7 +9,7 @@ use serde::Deserialize; use tokio::time::Duration; use torrust_rest_tracker_api_client::connection_info::{ConnectionInfo, Origin}; use torrust_rest_tracker_api_client::v1::client::Client as TrackerApiClient; -use torrust_tracker_lib::{app, bootstrap}; +use torrust_tracker_lib::app; #[tokio::test] async fn the_stats_api_endpoint_should_return_the_global_stats() { @@ -48,11 +47,7 @@ async fn the_stats_api_endpoint_should_return_the_global_stats() { env::set_var("TORRUST_TRACKER_CONFIG_TOML", config_with_two_http_trackers); - let (config, app_container) = bootstrap::app::setup(); - - let app_container = Arc::new(app_container); - - let _jobs = app::start(&config, &app_container).await; + let (_app_container, _jobs, _registar) = app::run().await; announce_to_tracker("http://127.0.0.1:7272").await; announce_to_tracker("http://127.0.0.1:7373").await; From 4e59dd7879b96a8f07c49725b2ab930d241d834b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 24 Mar 2025 12:36:35 +0000 Subject: [PATCH 4/6] refactor: [#1407] rename --- tests/servers/api/contract/stats/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/servers/api/contract/stats/mod.rs b/tests/servers/api/contract/stats/mod.rs index c31ab1907..a645fd7e1 100644 --- a/tests/servers/api/contract/stats/mod.rs +++ b/tests/servers/api/contract/stats/mod.rs @@ -52,9 +52,9 @@ async fn the_stats_api_endpoint_should_return_the_global_stats() { announce_to_tracker("http://127.0.0.1:7272").await; announce_to_tracker("http://127.0.0.1:7373").await; - let partial_metrics = get_partial_metrics("http://127.0.0.1:1414", "MyAccessToken").await; + let global_stats = get_tracker_statistics("http://127.0.0.1:1414", "MyAccessToken").await; - assert_eq!(partial_metrics.tcp4_announces_handled, 2); + assert_eq!(global_stats.tcp4_announces_handled, 2); } /// Make a sample announce request to the tracker. @@ -71,20 +71,20 @@ async fn announce_to_tracker(tracker_url: &str) { assert!(response.is_ok()); } -/// Metrics only relevant to the test. +/// Global statistics with only metrics relevant to the test. #[derive(Deserialize)] -struct PartialMetrics { +struct PartialGlobalStatistics { tcp4_announces_handled: u64, } -async fn get_partial_metrics(aip_url: &str, token: &str) -> PartialMetrics { +async fn get_tracker_statistics(aip_url: &str, token: &str) -> PartialGlobalStatistics { let response = TrackerApiClient::new(ConnectionInfo::authenticated(Origin::new(aip_url).unwrap(), token)) .unwrap() .get_tracker_statistics(None) .await; response - .json::() + .json::() .await .expect("Failed to parse JSON response") } From aff065cbbde622decf8555bb870e07efb9b3dde6 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 24 Mar 2025 13:10:21 +0000 Subject: [PATCH 5/6] fix: [#1407] docker build after adding new integration test --- .gitignore | 1 + tests/servers/api/contract/stats/mod.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 8bfa717b7..fd83ee918 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ /tracker.toml callgrind.out codecov.json +integration_tests_sqlite3.db lcov.info perf.data* rustc-ice-*.txt diff --git a/tests/servers/api/contract/stats/mod.rs b/tests/servers/api/contract/stats/mod.rs index a645fd7e1..016a372dd 100644 --- a/tests/servers/api/contract/stats/mod.rs +++ b/tests/servers/api/contract/stats/mod.rs @@ -30,6 +30,10 @@ async fn the_stats_api_endpoint_should_return_the_global_stats() { listed = false private = false + [core.database] + driver = "sqlite3" + path = "./integration_tests_sqlite3.db" + [[http_trackers]] bind_address = "0.0.0.0:7272" tracker_usage_statistics = true From b53da0736078719364fa98e07071ce8adc90b63c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 24 Mar 2025 13:11:28 +0000 Subject: [PATCH 6/6] refactor: [#1407] remove duplicate code --- src/console/profiling.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/console/profiling.rs b/src/console/profiling.rs index ffbd835fb..426712c34 100644 --- a/src/console/profiling.rs +++ b/src/console/profiling.rs @@ -157,12 +157,11 @@ //! kcachegrind callgrind.out //! ``` use std::env; -use std::sync::Arc; use std::time::Duration; use tokio::time::sleep; -use crate::{app, bootstrap}; +use crate::app; pub async fn run() { // Parse command line arguments @@ -180,11 +179,7 @@ pub async fn run() { return; }; - let (config, app_container) = bootstrap::app::setup(); - - let app_container = Arc::new(app_container); - - let (jobs, _registar) = app::start(&config, &app_container).await; + let (_app_container, jobs, _registar) = app::run().await; // Run the tracker for a fixed duration let run_duration = sleep(Duration::from_secs(duration_secs));