Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul core Tracker: extract IoC Container #1188

51 changes: 21 additions & 30 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,23 @@
//! - UDP trackers: the user can enable multiple UDP tracker on several ports.
//! - HTTP trackers: the user can enable multiple HTTP tracker on several ports.
//! - Tracker REST API: the tracker API can be enabled/disabled.
use std::sync::Arc;

use tokio::sync::RwLock;
use tokio::task::JoinHandle;
use torrust_tracker_configuration::Configuration;
use tracing::instrument;

use crate::bootstrap::jobs::{health_check_api, http_tracker, torrent_cleanup, tracker_apis, udp_tracker};
use crate::core::statistics::event::sender::Sender;
use crate::core::statistics::repository::Repository;
use crate::container::AppContainer;
use crate::servers;
use crate::servers::registar::Registar;
use crate::servers::udp::server::banning::BanService;
use crate::{core, servers};

/// # Panics
///
/// Will panic if:
///
/// - Can't retrieve tracker keys from database.
/// - Can't load whitelist from database.
#[instrument(skip(config, tracker, ban_service, stats_event_sender, stats_repository))]
pub async fn start(
config: &Configuration,
tracker: Arc<core::Tracker>,
ban_service: Arc<RwLock<BanService>>,
stats_event_sender: Arc<Option<Box<dyn Sender>>>,
stats_repository: Arc<Repository>,
) -> Vec<JoinHandle<()>> {
#[instrument(skip(config, app_container))]
pub async fn start(config: &Configuration, app_container: &AppContainer) -> Vec<JoinHandle<()>> {
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))
Expand All @@ -61,16 +50,18 @@ pub async fn start(
let registar = Registar::default();

// Load peer keys
if tracker.is_private() {
tracker
if app_container.tracker.is_private() {
app_container
.tracker
.load_keys_from_database()
.await
.expect("Could not retrieve keys from database.");
}

// Load whitelisted torrents
if tracker.is_listed() {
tracker
if app_container.tracker.is_listed() {
app_container
.tracker
.whitelist_manager
.load_whitelist_from_database()
.await
Expand All @@ -80,7 +71,7 @@ pub async fn start(
// Start the UDP blocks
if let Some(udp_trackers) = &config.udp_trackers {
for udp_tracker_config in udp_trackers {
if tracker.is_private() {
if app_container.tracker.is_private() {
tracing::warn!(
"Could not start UDP tracker on: {} while in private mode. UDP is not safe for private trackers!",
udp_tracker_config.bind_address
Expand All @@ -89,9 +80,9 @@ pub async fn start(
jobs.push(
udp_tracker::start_job(
udp_tracker_config,
tracker.clone(),
stats_event_sender.clone(),
ban_service.clone(),
app_container.tracker.clone(),
app_container.stats_event_sender.clone(),
app_container.ban_service.clone(),
registar.give_form(),
)
.await,
Expand All @@ -107,8 +98,8 @@ pub async fn start(
for http_tracker_config in http_trackers {
if let Some(job) = http_tracker::start_job(
http_tracker_config,
tracker.clone(),
stats_event_sender.clone(),
app_container.tracker.clone(),
app_container.stats_event_sender.clone(),
registar.give_form(),
servers::http::Version::V1,
)
Expand All @@ -125,10 +116,10 @@ pub async fn start(
if let Some(http_api_config) = &config.http_api {
if let Some(job) = tracker_apis::start_job(
http_api_config,
tracker.clone(),
ban_service.clone(),
stats_event_sender.clone(),
stats_repository.clone(),
app_container.tracker.clone(),
app_container.ban_service.clone(),
app_container.stats_event_sender.clone(),
app_container.stats_repository.clone(),
registar.give_form(),
servers::apis::Version::V1,
)
Expand All @@ -142,7 +133,7 @@ pub async fn start(

// Start runners to remove torrents without peers, every interval
if config.core.inactive_peer_cleanup_interval > 0 {
jobs.push(torrent_cleanup::start_job(&config.core, &tracker));
jobs.push(torrent_cleanup::start_job(&config.core, &app_container.tracker));
}

// Start Health Check API
Expand Down
18 changes: 18 additions & 0 deletions src/app_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//! This file contains only functions used for testing.
use std::sync::Arc;

use torrust_tracker_configuration::Configuration;

use crate::core::databases::Database;
use crate::core::services::{initialize_database, initialize_whitelist};
use crate::core::whitelist::WhiteListManager;

/// Initialize the tracker dependencies.
#[allow(clippy::type_complexity)]
#[must_use]
pub fn initialize_tracker_dependencies(config: &Configuration) -> (Arc<Box<dyn Database>>, Arc<WhiteListManager>) {
let database = initialize_database(config);
let whitelist_manager = initialize_whitelist(database.clone());

(database, whitelist_manager)
}
82 changes: 30 additions & 52 deletions src/bootstrap/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,21 @@ use tracing::instrument;

use super::config::initialize_configuration;
use crate::bootstrap;
use crate::core::databases::Database;
use crate::core::services::{initialize_database, initialize_whitelist, statistics, tracker_factory};
use crate::core::statistics::event::sender::Sender;
use crate::core::statistics::repository::Repository;
use crate::core::whitelist::WhiteListManager;
use crate::core::Tracker;
use crate::container::AppContainer;
use crate::core::services::{initialize_database, initialize_tracker, initialize_whitelist, statistics};
use crate::servers::udp::server::banning::BanService;
use crate::servers::udp::server::launcher::MAX_CONNECTION_ID_ERRORS_PER_IP;
use crate::shared::crypto::ephemeral_instance_keys;
use crate::shared::crypto::keys::{self, Keeper as _};

/// It loads the configuration from the environment and builds the main domain [`Tracker`] struct.
/// It loads the configuration from the environment and builds app container.
///
/// # Panics
///
/// Setup can file if the configuration is invalid.
#[must_use]
#[allow(clippy::type_complexity)]
#[instrument(skip())]
pub fn setup() -> (
Configuration,
Arc<Tracker>,
Arc<RwLock<BanService>>,
Arc<Option<Box<dyn Sender>>>,
Arc<Repository>,
) {
pub fn setup() -> (Configuration, AppContainer) {
#[cfg(not(test))]
check_seed();

Expand All @@ -56,19 +45,13 @@ pub fn setup() -> (
panic!("Configuration error: {e}");
}

// Initialize services

let (stats_event_sender, stats_repository) = statistics::setup::factory(configuration.core.tracker_usage_statistics);
let stats_event_sender = Arc::new(stats_event_sender);
let stats_repository = Arc::new(stats_repository);

let udp_ban_service = Arc::new(RwLock::new(BanService::new(MAX_CONNECTION_ID_ERRORS_PER_IP)));

let tracker = initialize_with_configuration(&configuration);
initialize_global_services(&configuration);

tracing::info!("Configuration:\n{}", configuration.clone().mask_secrets().to_json());

(configuration, tracker, udp_ban_service, stats_event_sender, stats_repository)
let app_container = initialize_app_container(&configuration);

(configuration, app_container)
}

/// checks if the seed is the instance seed in production.
Expand All @@ -83,15 +66,31 @@ pub fn check_seed() {
assert_eq!(seed, instance, "maybe using zeroed seed in production!?");
}

/// It initializes the application with the given configuration.
///
/// The configuration may be obtained from the environment (via config file or env vars).
#[must_use]
/// It initializes the global services.
#[instrument(skip())]
pub fn initialize_with_configuration(configuration: &Configuration) -> Arc<Tracker> {
pub fn initialize_global_services(configuration: &Configuration) {
initialize_static();
initialize_logging(configuration);
Arc::new(initialize_tracker(configuration))
}

/// It initializes the IoC Container.
#[instrument(skip())]
pub fn initialize_app_container(configuration: &Configuration) -> AppContainer {
let (stats_event_sender, stats_repository) = statistics::setup::factory(configuration.core.tracker_usage_statistics);
let stats_event_sender = Arc::new(stats_event_sender);
let stats_repository = Arc::new(stats_repository);
let ban_service = Arc::new(RwLock::new(BanService::new(MAX_CONNECTION_ID_ERRORS_PER_IP)));
let database = initialize_database(configuration);
let whitelist_manager = initialize_whitelist(database.clone());
let tracker = Arc::new(initialize_tracker(configuration, &database, &whitelist_manager));

AppContainer {
tracker,
ban_service,
stats_event_sender,
stats_repository,
whitelist_manager,
}
}

/// It initializes the application static values.
Expand All @@ -115,27 +114,6 @@ pub fn initialize_static() {
lazy_static::initialize(&ephemeral_instance_keys::ZEROED_TEST_CIPHER_BLOWFISH);
}

/// It builds the domain tracker
///
/// The tracker is the domain layer service. It's the entrypoint to make requests to the domain layer.
/// It's used by other higher-level components like the UDP and HTTP trackers or the tracker API.
#[must_use]
#[instrument(skip(config))]
pub fn initialize_tracker(config: &Configuration) -> Tracker {
let (database, whitelist_manager) = initialize_tracker_dependencies(config);

tracker_factory(config, &database, &whitelist_manager)
}

#[allow(clippy::type_complexity)]
#[must_use]
pub fn initialize_tracker_dependencies(config: &Configuration) -> (Arc<Box<dyn Database>>, Arc<WhiteListManager>) {
let database = initialize_database(config);
let whitelist_manager = initialize_whitelist(database.clone());

(database, whitelist_manager)
}

/// It initializes the log threshold, format and channel.
///
/// See [the logging setup](crate::bootstrap::logging::setup) for more info about logging.
Expand Down
13 changes: 10 additions & 3 deletions src/bootstrap/jobs/http_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ mod tests {

use torrust_tracker_test_helpers::configuration::ephemeral_public;

use crate::bootstrap::app::initialize_with_configuration;
use crate::bootstrap::app::initialize_global_services;
use crate::bootstrap::jobs::http_tracker::start_job;
use crate::core::services::statistics;
use crate::core::services::{initialize_database, initialize_tracker, initialize_whitelist, statistics};
use crate::servers::http::Version;
use crate::servers::registar::Registar;

Expand All @@ -97,9 +97,16 @@ mod tests {
let cfg = Arc::new(ephemeral_public());
let http_tracker = cfg.http_trackers.clone().expect("missing HTTP tracker configuration");
let config = &http_tracker[0];

let (stats_event_sender, _stats_repository) = statistics::setup::factory(cfg.core.tracker_usage_statistics);
let stats_event_sender = Arc::new(stats_event_sender);
let tracker = initialize_with_configuration(&cfg);

initialize_global_services(&cfg);

let database = initialize_database(&cfg);
let whitelist_manager = initialize_whitelist(database.clone());
let tracker = Arc::new(initialize_tracker(&cfg, &database, &whitelist_manager));

let version = Version::V1;

start_job(config, tracker, stats_event_sender, Registar::default().give_form(), version)
Expand Down
10 changes: 7 additions & 3 deletions src/bootstrap/jobs/tracker_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ mod tests {
use tokio::sync::RwLock;
use torrust_tracker_test_helpers::configuration::ephemeral_public;

use crate::bootstrap::app::initialize_with_configuration;
use crate::bootstrap::app::initialize_global_services;
use crate::bootstrap::jobs::tracker_apis::start_job;
use crate::core::services::statistics;
use crate::core::services::{initialize_database, initialize_tracker, initialize_whitelist, statistics};
use crate::servers::apis::Version;
use crate::servers::registar::Registar;
use crate::servers::udp::server::banning::BanService;
Expand All @@ -158,7 +158,11 @@ mod tests {
let stats_event_sender = Arc::new(stats_event_sender);
let stats_repository = Arc::new(stats_repository);

let tracker = initialize_with_configuration(&cfg);
initialize_global_services(&cfg);

let database = initialize_database(&cfg);
let whitelist_manager = initialize_whitelist(database.clone());
let tracker = Arc::new(initialize_tracker(&cfg, &database, &whitelist_manager));

let version = Version::V1;

Expand Down
4 changes: 2 additions & 2 deletions src/console/profiling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,9 @@ pub async fn run() {
return;
};

let (config, tracker, ban_service, stats_event_sender, stats_repository) = bootstrap::app::setup();
let (config, app_container) = bootstrap::app::setup();

let jobs = app::start(&config, tracker, ban_service, stats_event_sender, stats_repository).await;
let jobs = app::start(&config, &app_container).await;

// Run the tracker for a fixed duration
let run_duration = sleep(Duration::from_secs(duration_secs));
Expand Down
17 changes: 17 additions & 0 deletions src/container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::sync::Arc;

use tokio::sync::RwLock;

use crate::core::statistics::event::sender::Sender;
use crate::core::statistics::repository::Repository;
use crate::core::whitelist::WhiteListManager;
use crate::core::Tracker;
use crate::servers::udp::server::banning::BanService;

pub struct AppContainer {
pub tracker: Arc<Tracker>,
pub ban_service: Arc<RwLock<BanService>>,
pub stats_event_sender: Arc<Option<Box<dyn Sender>>>,
pub stats_repository: Arc<Repository>,
pub whitelist_manager: Arc<WhiteListManager>,
}
12 changes: 6 additions & 6 deletions src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1080,28 +1080,28 @@ mod tests {
use torrust_tracker_primitives::DurationSinceUnixEpoch;
use torrust_tracker_test_helpers::configuration;

use crate::bootstrap::app::initialize_tracker_dependencies;
use crate::app_test::initialize_tracker_dependencies;
use crate::core::peer::Peer;
use crate::core::services::tracker_factory;
use crate::core::services::initialize_tracker;
use crate::core::whitelist::WhiteListManager;
use crate::core::{TorrentsMetrics, Tracker};

fn public_tracker() -> Tracker {
let config = configuration::ephemeral_public();
let (database, whitelist_manager) = initialize_tracker_dependencies(&config);
tracker_factory(&config, &database, &whitelist_manager)
initialize_tracker(&config, &database, &whitelist_manager)
}

fn private_tracker() -> Tracker {
let config = configuration::ephemeral_private();
let (database, whitelist_manager) = initialize_tracker_dependencies(&config);
tracker_factory(&config, &database, &whitelist_manager)
initialize_tracker(&config, &database, &whitelist_manager)
}

fn whitelisted_tracker() -> (Tracker, Arc<WhiteListManager>) {
let config = configuration::ephemeral_listed();
let (database, whitelist_manager) = initialize_tracker_dependencies(&config);
let tracker = tracker_factory(&config, &database, &whitelist_manager);
let tracker = initialize_tracker(&config, &database, &whitelist_manager);

(tracker, whitelist_manager)
}
Expand All @@ -1110,7 +1110,7 @@ mod tests {
let mut config = configuration::ephemeral_listed();
config.core.tracker_policy.persistent_torrent_completed_stat = true;
let (database, whitelist_manager) = initialize_tracker_dependencies(&config);
tracker_factory(&config, &database, &whitelist_manager)
initialize_tracker(&config, &database, &whitelist_manager)
}

fn sample_info_hash() -> InfoHash {
Expand Down
Loading
Loading