Skip to content

Commit 13308d5

Browse files
committed
dev: add tests to health check
1 parent 2baaf7c commit 13308d5

31 files changed

+998
-967
lines changed

packages/test-helpers/src/configuration.rs

+12
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,15 @@ pub fn ephemeral_with_no_services() -> Configuration {
156156

157157
cfg
158158
}
159+
160+
/// Ephemeral with only udp
161+
#[must_use]
162+
pub fn ephemeral_udp_only() -> Configuration {
163+
let mut cfg = ephemeral();
164+
165+
cfg.http_api.enabled = false;
166+
cfg.http_trackers[0].enabled = false;
167+
cfg.udp_trackers[0].enabled = false;
168+
169+
cfg
170+
}

src/bootstrap/jobs/health_check_api.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use torrust_tracker_configuration::HealthCheckApi;
2222
use super::Started;
2323
use crate::app::Registry;
2424
use crate::servers::health_check_api::server;
25+
use crate::servers::signals::Halted;
2526

2627
/// This function starts a new Health Check API server with the provided
2728
/// configuration.
@@ -40,12 +41,14 @@ pub async fn start_job(config: &HealthCheckApi, register: Registry) -> JoinHandl
4041
.expect("it should have a valid health check bind address");
4142

4243
let (tx_start, rx_start) = oneshot::channel::<Started>();
44+
let (tx_halt, rx_halt) = tokio::sync::oneshot::channel::<Halted>();
45+
drop(tx_halt);
4346

4447
// Run the API server
4548
let join_handle = tokio::spawn(async move {
4649
info!(target: "Health Check API", "Starting on: http://{}", bind_addr);
4750

48-
let handle = server::start(bind_addr, tx_start, register);
51+
let handle = server::start(bind_addr, tx_start, rx_halt, register);
4952

5053
if let Ok(()) = handle.await {
5154
info!(target: "Health Check API", "Stopped server running on: http://{}", bind_addr);

src/servers/health_check_api/handlers.rs

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ pub(crate) async fn health_check_handler(State(register): State<Registry>) -> Js
2121
checks = mutex.await.iter().map(|(binding, check_fn)| check_fn(binding)).collect();
2222
}
2323

24+
// if we do not have any checks, lets return a `none` result.
25+
if checks.is_empty() {
26+
return responses::none();
27+
}
28+
2429
let jobs = checks.drain(..).map(|c| {
2530
tokio::spawn(async move {
2631
CheckReport {

src/servers/health_check_api/resources.rs

+10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
66
pub enum Status {
77
Ok,
88
Error,
9+
None,
910
}
1011

1112
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
@@ -34,6 +35,15 @@ pub struct Report {
3435
}
3536

3637
impl Report {
38+
#[must_use]
39+
pub fn none() -> Report {
40+
Self {
41+
status: Status::None,
42+
message: String::new(),
43+
details: Vec::default(),
44+
}
45+
}
46+
3747
#[must_use]
3848
pub fn ok(details: Vec<CheckReport>) -> Report {
3949
Self {

src/servers/health_check_api/responses.rs

+4
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ pub fn ok(details: Vec<CheckReport>) -> Json<Report> {
99
pub fn error(message: String, details: Vec<CheckReport>) -> Json<Report> {
1010
Json(Report::error(message, details))
1111
}
12+
13+
pub fn none() -> Json<Report> {
14+
Json(Report::none())
15+
}

src/servers/health_check_api/server.rs

+18-14
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,44 @@ use axum::routing::get;
88
use axum::{Json, Router};
99
use axum_server::Handle;
1010
use futures::Future;
11-
use log::info;
1211
use serde_json::json;
13-
use tokio::sync::oneshot::Sender;
12+
use tokio::sync::oneshot::{Receiver, Sender};
1413

1514
use crate::app::Registry;
1615
use crate::bootstrap::jobs::Started;
1716
use crate::servers::health_check_api::handlers::health_check_handler;
17+
use crate::servers::signals::{graceful_shutdown, Halted};
1818

1919
/// Starts Health Check API server.
2020
///
2121
/// # Panics
2222
///
2323
/// Will panic if binding to the socket address fails.
24-
pub fn start(address: SocketAddr, tx: Sender<Started>, register: Registry) -> impl Future<Output = Result<(), std::io::Error>> {
25-
let app = Router::new()
24+
pub fn start(
25+
bind_to: SocketAddr,
26+
tx: Sender<Started>,
27+
rx_halt: Receiver<Halted>,
28+
register: Registry,
29+
) -> impl Future<Output = Result<(), std::io::Error>> {
30+
let router = Router::new()
2631
.route("/", get(|| async { Json(json!({})) }))
2732
.route("/health_check", get(health_check_handler))
2833
.with_state(register);
2934

30-
let handle = Handle::new();
31-
let cloned_handle = handle.clone();
32-
33-
let socket = std::net::TcpListener::bind(address).expect("Could not bind tcp_listener to address.");
35+
let socket = std::net::TcpListener::bind(bind_to).expect("Could not bind tcp_listener to address.");
3436
let address = socket.local_addr().expect("Could not get local_addr from tcp_listener.");
3537

36-
tokio::task::spawn(async move {
37-
tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal.");
38-
info!("Stopping Torrust Health Check API server o http://{} ...", address);
39-
cloned_handle.shutdown();
40-
});
38+
let handle = Handle::new();
39+
40+
tokio::task::spawn(graceful_shutdown(
41+
handle.clone(),
42+
rx_halt,
43+
format!("shutting down http server on socket address: {address}"),
44+
));
4145

4246
let running = axum_server::from_tcp(socket)
4347
.handle(handle)
44-
.serve(app.into_make_service_with_connect_info::<SocketAddr>());
48+
.serve(router.into_make_service_with_connect_info::<SocketAddr>());
4549

4650
tx.send(Started { address })
4751
.expect("the Health Check API server should not be dropped");

src/shared/bit_torrent/udp/client.rs

+21-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use std::io::Cursor;
22
use std::net::SocketAddr;
33
use std::sync::Arc;
4+
use std::time::Duration;
45

56
use aquatic_udp_protocol::{ConnectRequest, Request, Response, TransactionId};
67
use tokio::net::UdpSocket;
8+
use tokio::time;
79

810
use crate::shared::bit_torrent::udp::{source_address, MAX_PACKET_SIZE};
911

@@ -112,6 +114,8 @@ pub async fn new_udp_tracker_client_connected(remote_address: &str) -> UdpTracke
112114
/// # Errors
113115
///
114116
/// It will return an error if unable to connect to the UDP service.
117+
///
118+
/// # Panics
115119
pub async fn check(binding: &SocketAddr) -> Result<String, String> {
116120
let client = new_udp_tracker_client_connected(binding.to_string().as_str()).await;
117121

@@ -121,11 +125,23 @@ pub async fn check(binding: &SocketAddr) -> Result<String, String> {
121125

122126
client.send(connect_request.into()).await;
123127

124-
let response = client.receive().await;
128+
let process = move |response| {
129+
if matches!(response, Response::Connect(_connect_response)) {
130+
Ok("Connected".to_string())
131+
} else {
132+
Err("Did not Connect".to_string())
133+
}
134+
};
135+
136+
let sleep = time::sleep(Duration::from_millis(2000));
137+
tokio::pin!(sleep);
125138

126-
if matches!(response, Response::Connect(_connect_response)) {
127-
Ok("Connected".to_string())
128-
} else {
129-
Err("Did not Connect".to_string())
139+
tokio::select! {
140+
() = &mut sleep => {
141+
Err("Timed Out".to_string())
142+
}
143+
response = client.receive() => {
144+
process(response)
145+
}
130146
}
131147
}

tests/common/app.rs

-8
This file was deleted.

tests/common/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
pub mod app;
21
pub mod fixtures;
32
pub mod http;
43
pub mod udp;

tests/servers/api/environment.rs

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use std::sync::Arc;
2+
3+
use futures::executor::block_on;
4+
use torrust_tracker::app::Registar;
5+
use torrust_tracker::bootstrap::app::initialize_with_configuration;
6+
use torrust_tracker::bootstrap::jobs::make_rust_tls;
7+
use torrust_tracker::core::peer::Peer;
8+
use torrust_tracker::core::Tracker;
9+
use torrust_tracker::servers::apis::server::{ApiServer, Launcher, Running, Stopped};
10+
use torrust_tracker::shared::bit_torrent::info_hash::InfoHash;
11+
use torrust_tracker_configuration::{Configuration, HttpApi};
12+
13+
use super::connection_info::ConnectionInfo;
14+
15+
pub struct Environment<S> {
16+
pub config: Arc<HttpApi>,
17+
pub tracker: Arc<Tracker>,
18+
pub registar: Registar,
19+
pub server: ApiServer<S>,
20+
}
21+
22+
impl<S> Environment<S> {
23+
/// Add a torrent to the tracker
24+
pub async fn add_torrent_peer(&self, info_hash: &InfoHash, peer: &Peer) {
25+
self.tracker.update_torrent_with_peer_and_get_stats(info_hash, peer).await;
26+
}
27+
}
28+
29+
impl Environment<Stopped> {
30+
pub fn new(configuration: &Arc<Configuration>) -> Self {
31+
let tracker = initialize_with_configuration(configuration);
32+
33+
let config = Arc::new(configuration.http_api.clone());
34+
35+
let bind_to = config
36+
.bind_address
37+
.parse::<std::net::SocketAddr>()
38+
.expect("Tracker API bind_address invalid.");
39+
40+
let tls = block_on(make_rust_tls(config.ssl_enabled, &config.ssl_cert_path, &config.ssl_key_path))
41+
.map(|tls| tls.expect("tls config failed"));
42+
43+
let server = ApiServer::new(Launcher::new(bind_to, tls));
44+
45+
Self {
46+
config,
47+
tracker,
48+
registar: Registar::default(),
49+
server,
50+
}
51+
}
52+
53+
pub async fn start(self) -> Environment<Running> {
54+
let access_tokens = Arc::new(self.config.access_tokens.clone());
55+
56+
Environment {
57+
config: self.config,
58+
tracker: self.tracker.clone(),
59+
registar: self.registar.clone(),
60+
server: self
61+
.server
62+
.start(self.tracker, self.registar.give_form(), access_tokens)
63+
.await
64+
.unwrap(),
65+
}
66+
}
67+
}
68+
69+
impl Environment<Running> {
70+
pub async fn new(configuration: &Arc<Configuration>) -> Self {
71+
Environment::<Stopped>::new(configuration).start().await
72+
}
73+
74+
pub async fn stop(self) -> Environment<Stopped> {
75+
Environment {
76+
config: self.config,
77+
tracker: self.tracker,
78+
registar: Registar::default(),
79+
server: self.server.stop().await.unwrap(),
80+
}
81+
}
82+
83+
pub fn get_connection_info(&self) -> ConnectionInfo {
84+
ConnectionInfo {
85+
bind_address: self.server.state.binding.to_string(),
86+
api_token: self.config.access_tokens.get("admin").cloned(),
87+
}
88+
}
89+
}

tests/servers/api/mod.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
use std::sync::Arc;
22

33
use torrust_tracker::core::Tracker;
4+
use torrust_tracker::servers::apis::server;
45

56
pub mod connection_info;
6-
pub mod test_environment;
7+
pub mod environment;
78
pub mod v1;
89

10+
pub type Started = environment::Environment<server::Running>;
11+
912
/// It forces a database error by dropping all tables.
1013
/// That makes any query fail.
1114
/// code-review: alternatively we could inject a database mock in the future.

0 commit comments

Comments
 (0)