Skip to content

Commit 5f6eed7

Browse files
committed
Merge #561: Cleanup Service Initialization
13140f6 dev: cleanup service bootstraping (Cameron Garnham) Pull request description: Extracted from: - #557 Closes #560 ACKs for top commit: da2ce7: ACK 13140f6 Tree-SHA512: 70eb31e6a0c8bb93c48ca90227763c33c4c90055cacb11bc4dfc73ab9813c0833bcdfb7c731b85169d7366774671cdf72e2cc91e51b1ce289708494fc90e4e54
2 parents 051a0c8 + 13140f6 commit 5f6eed7

File tree

25 files changed

+804
-835
lines changed

25 files changed

+804
-835
lines changed

packages/configuration/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ use config::{Config, ConfigError, File, FileFormat};
239239
use serde::{Deserialize, Serialize};
240240
use serde_with::{serde_as, NoneAsEmptyString};
241241
use thiserror::Error;
242-
use torrust_tracker_located_error::{Located, LocatedError};
242+
use torrust_tracker_located_error::{DynError, Located, LocatedError};
243243
use torrust_tracker_primitives::{DatabaseDriver, TrackerMode};
244244

245245
/// Information required for loading config
@@ -289,7 +289,7 @@ impl Info {
289289

290290
fs::read_to_string(config_path)
291291
.map_err(|e| Error::UnableToLoadFromConfigFile {
292-
source: (Arc::new(e) as Arc<dyn std::error::Error + Send + Sync>).into(),
292+
source: (Arc::new(e) as DynError).into(),
293293
})?
294294
.parse()
295295
.map_err(|_e: std::convert::Infallible| Error::Infallible)?

packages/located-error/src/lib.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ use std::error::Error;
3333
use std::panic::Location;
3434
use std::sync::Arc;
3535

36+
use log::debug;
37+
38+
pub type DynError = Arc<dyn std::error::Error + Send + Sync>;
39+
3640
/// A generic wrapper around an error.
3741
///
3842
/// Where `E` is the inner error (source error).
@@ -90,13 +94,13 @@ where
9094
source: Arc::new(self.0),
9195
location: Box::new(*std::panic::Location::caller()),
9296
};
93-
log::debug!("{e}");
97+
debug!("{e}");
9498
e
9599
}
96100
}
97101

98102
#[allow(clippy::from_over_into)]
99-
impl<'a> Into<LocatedError<'a, dyn std::error::Error + Send + Sync>> for Arc<dyn std::error::Error + Send + Sync> {
103+
impl<'a> Into<LocatedError<'a, dyn std::error::Error + Send + Sync>> for DynError {
100104
#[track_caller]
101105
fn into(self) -> LocatedError<'a, dyn std::error::Error + Send + Sync> {
102106
LocatedError {

src/app.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ use tokio::task::JoinHandle;
2828
use torrust_tracker_configuration::Configuration;
2929

3030
use crate::bootstrap::jobs::{health_check_api, http_tracker, torrent_cleanup, tracker_apis, udp_tracker};
31-
use crate::core;
32-
use crate::servers::http::Version;
31+
use crate::{core, servers};
3332

3433
/// # Panics
3534
///
@@ -68,21 +67,22 @@ pub async fn start(config: Arc<Configuration>, tracker: Arc<core::Tracker>) -> V
6867
udp_tracker_config.bind_address, config.mode
6968
);
7069
} else {
71-
jobs.push(udp_tracker::start_job(udp_tracker_config, tracker.clone()));
70+
jobs.push(udp_tracker::start_job(udp_tracker_config, tracker.clone()).await);
7271
}
7372
}
7473

7574
// Start the HTTP blocks
7675
for http_tracker_config in &config.http_trackers {
77-
if !http_tracker_config.enabled {
78-
continue;
79-
}
80-
jobs.push(http_tracker::start_job(http_tracker_config, tracker.clone(), Version::V1).await);
76+
if let Some(job) = http_tracker::start_job(http_tracker_config, tracker.clone(), servers::http::Version::V1).await {
77+
jobs.push(job);
78+
};
8179
}
8280

8381
// Start HTTP API
8482
if config.http_api.enabled {
85-
jobs.push(tracker_apis::start_job(&config.http_api, tracker.clone()).await);
83+
if let Some(job) = tracker_apis::start_job(&config.http_api, tracker.clone(), servers::apis::Version::V1).await {
84+
jobs.push(job);
85+
};
8686
}
8787

8888
// Start runners to remove torrents without peers, every interval

src/bootstrap/jobs/health_check_api.rs

+7-20
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,23 @@
66
//! The [`health_check_api::start_job`](crate::bootstrap::jobs::health_check_api::start_job)
77
//! function spawns a new asynchronous task, that tasks is the "**launcher**".
88
//! The "**launcher**" starts the actual server and sends a message back
9-
//! to the main application. The main application waits until receives
10-
//! the message [`ApiServerJobStarted`]
11-
//! from the "**launcher**".
9+
//! to the main application.
1210
//!
1311
//! The "**launcher**" is an intermediary thread that decouples the Health Check
1412
//! API server from the process that handles it.
1513
//!
1614
//! Refer to the [configuration documentation](https://docs.rs/torrust-tracker-configuration)
1715
//! for the API configuration options.
18-
use std::net::SocketAddr;
1916
use std::sync::Arc;
2017

2118
use log::info;
2219
use tokio::sync::oneshot;
2320
use tokio::task::JoinHandle;
2421
use torrust_tracker_configuration::Configuration;
2522

23+
use super::Started;
2624
use crate::servers::health_check_api::server;
2725

28-
/// This is the message that the "launcher" spawned task sends to the main
29-
/// application process to notify the API server was successfully started.
30-
///
31-
/// > **NOTICE**: it does not mean the API server is ready to receive requests.
32-
/// It only means the new server started. It might take some time to the server
33-
/// to be ready to accept request.
34-
#[derive(Debug)]
35-
pub struct ApiServerJobStarted {
36-
pub bound_addr: SocketAddr,
37-
}
38-
3926
/// This function starts a new Health Check API server with the provided
4027
/// configuration.
4128
///
@@ -51,24 +38,24 @@ pub async fn start_job(config: Arc<Configuration>) -> JoinHandle<()> {
5138
.health_check_api
5239
.bind_address
5340
.parse::<std::net::SocketAddr>()
54-
.expect("Health Check API bind_address invalid.");
41+
.expect("it should have a valid health check bind address");
5542

56-
let (tx, rx) = oneshot::channel::<ApiServerJobStarted>();
43+
let (tx_start, rx_start) = oneshot::channel::<Started>();
5744

5845
// Run the API server
5946
let join_handle = tokio::spawn(async move {
6047
info!("Starting Health Check API server: http://{}", bind_addr);
6148

62-
let handle = server::start(bind_addr, tx, config.clone());
49+
let handle = server::start(bind_addr, tx_start, config.clone());
6350

6451
if let Ok(()) = handle.await {
6552
info!("Health Check API server on http://{} stopped", bind_addr);
6653
}
6754
});
6855

6956
// Wait until the API server job is running
70-
match rx.await {
71-
Ok(_msg) => info!("Torrust Health Check API server started"),
57+
match rx_start.await {
58+
Ok(msg) => info!("Torrust Health Check API server started on socket: {}", msg.address),
7259
Err(e) => panic!("the Health Check API server was dropped: {e}"),
7360
}
7461

src/bootstrap/jobs/http_tracker.rs

+54-57
Original file line numberDiff line numberDiff line change
@@ -7,88 +7,85 @@
77
//!
88
//! The [`http_tracker::start_job`](crate::bootstrap::jobs::http_tracker::start_job) function spawns a new asynchronous task,
99
//! that tasks is the "**launcher**". The "**launcher**" starts the actual server and sends a message back to the main application.
10-
//! The main application waits until receives the message [`ServerJobStarted`] from the "**launcher**".
1110
//!
1211
//! 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.
1312
//! In that case it would not need to notify a parent process.
13+
use std::net::SocketAddr;
1414
use std::sync::Arc;
1515

1616
use axum_server::tls_rustls::RustlsConfig;
1717
use log::info;
18-
use tokio::sync::oneshot;
1918
use tokio::task::JoinHandle;
2019
use torrust_tracker_configuration::HttpTracker;
2120

21+
use super::make_rust_tls;
2222
use crate::core;
23-
use crate::servers::http::v1::launcher;
23+
use crate::servers::http::server::{HttpServer, Launcher};
2424
use crate::servers::http::Version;
2525

26-
/// This is the message that the "**launcher**" spawned task sends to the main application process to notify that the HTTP server was successfully started.
27-
///
28-
/// > **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.
29-
#[derive(Debug)]
30-
pub struct ServerJobStarted();
31-
3226
/// It starts a new HTTP server with the provided configuration and version.
3327
///
3428
/// Right now there is only one version but in the future we could support more than one HTTP tracker version at the same time.
3529
/// This feature allows supporting breaking changes on `BitTorrent` BEPs.
36-
pub async fn start_job(config: &HttpTracker, tracker: Arc<core::Tracker>, version: Version) -> JoinHandle<()> {
37-
match version {
38-
Version::V1 => start_v1(config, tracker.clone()).await,
39-
}
40-
}
41-
30+
///
4231
/// # Panics
4332
///
4433
/// It would panic if the `config::HttpTracker` struct would contain inappropriate values.
45-
async fn start_v1(config: &HttpTracker, tracker: Arc<core::Tracker>) -> JoinHandle<()> {
46-
let bind_addr = config
47-
.bind_address
48-
.parse::<std::net::SocketAddr>()
49-
.expect("Tracker API bind_address invalid.");
50-
let ssl_enabled = config.ssl_enabled;
51-
let ssl_cert_path = config.ssl_cert_path.clone();
52-
let ssl_key_path = config.ssl_key_path.clone();
53-
54-
let (tx, rx) = oneshot::channel::<ServerJobStarted>();
55-
56-
// Run the API server
57-
let join_handle = tokio::spawn(async move {
58-
if !ssl_enabled {
59-
info!("Starting Torrust HTTP tracker server on: http://{}", bind_addr);
60-
61-
let handle = launcher::start(bind_addr, tracker);
62-
63-
tx.send(ServerJobStarted())
64-
.expect("the HTTP tracker server should not be dropped");
34+
///
35+
pub async fn start_job(config: &HttpTracker, tracker: Arc<core::Tracker>, version: Version) -> Option<JoinHandle<()>> {
36+
if config.enabled {
37+
let socket = config
38+
.bind_address
39+
.parse::<std::net::SocketAddr>()
40+
.expect("it should have a valid http tracker bind address");
41+
42+
let tls = make_rust_tls(config.ssl_enabled, &config.ssl_cert_path, &config.ssl_key_path)
43+
.await
44+
.map(|tls| tls.expect("it should have a valid http tracker tls configuration"));
45+
46+
match version {
47+
Version::V1 => Some(start_v1(socket, tls, tracker.clone()).await),
48+
}
49+
} else {
50+
info!("Note: Not loading Http Tracker Service, Not Enabled in Configuration.");
51+
None
52+
}
53+
}
6554

66-
if let Ok(()) = handle.await {
67-
info!("Torrust HTTP tracker server on http://{} stopped", bind_addr);
68-
}
69-
} else if ssl_enabled && ssl_cert_path.is_some() && ssl_key_path.is_some() {
70-
info!("Starting Torrust HTTP tracker server on: https://{}", bind_addr);
55+
async fn start_v1(socket: SocketAddr, tls: Option<RustlsConfig>, tracker: Arc<core::Tracker>) -> JoinHandle<()> {
56+
let server = HttpServer::new(Launcher::new(socket, tls))
57+
.start(tracker)
58+
.await
59+
.expect("it should be able to start to the http tracker");
60+
61+
tokio::spawn(async move {
62+
server
63+
.state
64+
.task
65+
.await
66+
.expect("it should be able to join to the http tracker task");
67+
})
68+
}
7169

72-
let ssl_config = RustlsConfig::from_pem_file(ssl_cert_path.unwrap(), ssl_key_path.unwrap())
73-
.await
74-
.unwrap();
70+
#[cfg(test)]
71+
mod tests {
72+
use std::sync::Arc;
7573

76-
let handle = launcher::start_tls(bind_addr, ssl_config, tracker);
74+
use torrust_tracker_test_helpers::configuration::ephemeral_mode_public;
7775

78-
tx.send(ServerJobStarted())
79-
.expect("the HTTP tracker server should not be dropped");
76+
use crate::bootstrap::app::initialize_with_configuration;
77+
use crate::bootstrap::jobs::http_tracker::start_job;
78+
use crate::servers::http::Version;
8079

81-
if let Ok(()) = handle.await {
82-
info!("Torrust HTTP tracker server on https://{} stopped", bind_addr);
83-
}
84-
}
85-
});
80+
#[tokio::test]
81+
async fn it_should_start_http_tracker() {
82+
let cfg = Arc::new(ephemeral_mode_public());
83+
let config = &cfg.http_trackers[0];
84+
let tracker = initialize_with_configuration(&cfg);
85+
let version = Version::V1;
8686

87-
// Wait until the HTTP tracker server job is running
88-
match rx.await {
89-
Ok(_msg) => info!("Torrust HTTP tracker server started"),
90-
Err(e) => panic!("the HTTP tracker server was dropped: {e}"),
87+
start_job(config, tracker, version)
88+
.await
89+
.expect("it should be able to join to the http tracker start-job");
9190
}
92-
93-
join_handle
9491
}

src/bootstrap/jobs/mod.rs

+83
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,86 @@ pub mod http_tracker;
1111
pub mod torrent_cleanup;
1212
pub mod tracker_apis;
1313
pub mod udp_tracker;
14+
15+
/// This is the message that the "launcher" spawned task sends to the main
16+
/// application process to notify the service was successfully started.
17+
///
18+
#[derive(Debug)]
19+
pub struct Started {
20+
pub address: std::net::SocketAddr,
21+
}
22+
23+
pub async fn make_rust_tls(enabled: bool, cert: &Option<String>, key: &Option<String>) -> Option<Result<RustlsConfig, Error>> {
24+
if !enabled {
25+
info!("tls not enabled");
26+
return None;
27+
}
28+
29+
if let (Some(cert), Some(key)) = (cert, key) {
30+
info!("Using https: cert path: {cert}.");
31+
info!("Using https: key path: {cert}.");
32+
33+
Some(
34+
RustlsConfig::from_pem_file(cert, key)
35+
.await
36+
.map_err(|err| Error::BadTlsConfig {
37+
source: (Arc::new(err) as DynError).into(),
38+
}),
39+
)
40+
} else {
41+
Some(Err(Error::MissingTlsConfig {
42+
location: Location::caller(),
43+
}))
44+
}
45+
}
46+
47+
#[cfg(test)]
48+
mod tests {
49+
50+
use super::make_rust_tls;
51+
52+
#[tokio::test]
53+
async fn it_should_error_on_bad_tls_config() {
54+
let (bad_cert_path, bad_key_path) = (Some("bad cert path".to_string()), Some("bad key path".to_string()));
55+
let err = make_rust_tls(true, &bad_cert_path, &bad_key_path)
56+
.await
57+
.expect("tls_was_enabled")
58+
.expect_err("bad_cert_and_key_files");
59+
60+
assert!(err
61+
.to_string()
62+
.contains("bad tls config: No such file or directory (os error 2)"));
63+
}
64+
65+
#[tokio::test]
66+
async fn it_should_error_on_missing_tls_config() {
67+
let err = make_rust_tls(true, &None, &None)
68+
.await
69+
.expect("tls_was_enabled")
70+
.expect_err("missing_config");
71+
72+
assert_eq!(err.to_string(), "tls config missing");
73+
}
74+
}
75+
76+
use std::panic::Location;
77+
use std::sync::Arc;
78+
79+
use axum_server::tls_rustls::RustlsConfig;
80+
use log::info;
81+
use thiserror::Error;
82+
use torrust_tracker_located_error::{DynError, LocatedError};
83+
84+
/// Error returned by the Bootstrap Process.
85+
#[derive(Error, Debug)]
86+
pub enum Error {
87+
/// Enabled tls but missing config.
88+
#[error("tls config missing")]
89+
MissingTlsConfig { location: &'static Location<'static> },
90+
91+
/// Unable to parse tls Config.
92+
#[error("bad tls config: {source}")]
93+
BadTlsConfig {
94+
source: LocatedError<'static, dyn std::error::Error + Send + Sync>,
95+
},
96+
}

0 commit comments

Comments
 (0)