Skip to content

Commit 5959e42

Browse files
committed
refactor: [torrust#634] extract funtions
1 parent af65b59 commit 5959e42

File tree

6 files changed

+207
-102
lines changed

6 files changed

+207
-102
lines changed

src/e2e/docker.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
//! Docker command wrapper.
12
use std::io;
23
use std::process::{Command, Output};
34
use std::thread::sleep;
45
use std::time::{Duration, Instant};
56

67
use log::debug;
78

9+
/// Docker command wrapper.
810
pub struct Docker {}
911

1012
pub struct RunningContainer {

src/e2e/logs.rs

-35
This file was deleted.

src/e2e/logs_parser.rs

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//! Utilities to parse Torrust Tracker logs.
2+
use serde::{Deserialize, Serialize};
3+
4+
const UDP_TRACKER_PATTERN: &str = "[UDP Tracker][INFO] Starting on: udp://";
5+
const HTTP_TRACKER_PATTERN: &str = "[HTTP Tracker][INFO] Starting on: ";
6+
const HEALTH_CHECK_PATTERN: &str = "[Health Check API][INFO] Starting on: ";
7+
8+
#[derive(Serialize, Deserialize, Debug, Default)]
9+
pub struct RunningServices {
10+
pub udp_trackers: Vec<String>,
11+
pub http_trackers: Vec<String>,
12+
pub health_checks: Vec<String>,
13+
}
14+
15+
impl RunningServices {
16+
/// It parses the tracker logs to extract the running services.
17+
///
18+
/// For example, from this logs:
19+
///
20+
/// ```text
21+
/// Loading default configuration file: `./share/default/config/tracker.development.sqlite3.toml` ...
22+
/// 2024-01-24T16:36:14.614898789+00:00 [torrust_tracker::bootstrap::logging][INFO] logging initialized.
23+
/// 2024-01-24T16:36:14.615586025+00:00 [UDP Tracker][INFO] Starting on: udp://0.0.0.0:6969
24+
/// 2024-01-24T16:36:14.615623705+00:00 [torrust_tracker::bootstrap::jobs][INFO] TLS not enabled
25+
/// 2024-01-24T16:36:14.615694484+00:00 [HTTP Tracker][INFO] Starting on: http://0.0.0.0:7070
26+
/// 2024-01-24T16:36:14.615710534+00:00 [HTTP Tracker][INFO] Started on: http://0.0.0.0:7070
27+
/// 2024-01-24T16:36:14.615716574+00:00 [torrust_tracker::bootstrap::jobs][INFO] TLS not enabled
28+
/// 2024-01-24T16:36:14.615764904+00:00 [API][INFO] Starting on http://127.0.0.1:1212
29+
/// 2024-01-24T16:36:14.615767264+00:00 [API][INFO] Started on http://127.0.0.1:1212
30+
/// 2024-01-24T16:36:14.615777574+00:00 [Health Check API][INFO] Starting on: http://127.0.0.1:1313
31+
/// 2024-01-24T16:36:14.615791124+00:00 [Health Check API][INFO] Started on: http://127.0.0.1:1313
32+
/// ```
33+
///
34+
/// It would extract theses services:
35+
///
36+
/// ```json
37+
/// {
38+
/// "udp_trackers": [
39+
/// "127.0.0.1:6969"
40+
/// ],
41+
/// "http_trackers": [
42+
/// "http://127.0.0.1:7070"
43+
/// ],
44+
/// "health_checks": [
45+
/// "http://127.0.0.1:1313/health_check"
46+
/// ]
47+
/// }
48+
/// ```
49+
#[must_use]
50+
pub fn parse_from_logs(logs: &str) -> Self {
51+
let mut udp_trackers: Vec<String> = Vec::new();
52+
let mut http_trackers: Vec<String> = Vec::new();
53+
let mut health_checks: Vec<String> = Vec::new();
54+
55+
for line in logs.lines() {
56+
if let Some(address) = Self::extract_address_if_matches(line, UDP_TRACKER_PATTERN) {
57+
udp_trackers.push(address);
58+
} else if let Some(address) = Self::extract_address_if_matches(line, HTTP_TRACKER_PATTERN) {
59+
http_trackers.push(address);
60+
} else if let Some(address) = Self::extract_address_if_matches(line, HEALTH_CHECK_PATTERN) {
61+
health_checks.push(format!("{address}/health_check"));
62+
}
63+
}
64+
65+
Self {
66+
udp_trackers,
67+
http_trackers,
68+
health_checks,
69+
}
70+
}
71+
72+
fn extract_address_if_matches(line: &str, pattern: &str) -> Option<String> {
73+
line.find(pattern)
74+
.map(|start| Self::replace_wildcard_ip_with_localhost(line[start + pattern.len()..].trim()))
75+
}
76+
77+
fn replace_wildcard_ip_with_localhost(address: &str) -> String {
78+
address.replace("0.0.0.0", "127.0.0.1")
79+
}
80+
}
81+
82+
#[cfg(test)]
83+
mod tests {
84+
use super::*;
85+
86+
#[test]
87+
fn it_should_parse_from_logs_with_valid_logs() {
88+
let logs = "\
89+
[UDP Tracker][INFO] Starting on: udp://0.0.0.0:8080\n\
90+
[HTTP Tracker][INFO] Starting on: 0.0.0.0:9090\n\
91+
[Health Check API][INFO] Starting on: 0.0.0.0:10010";
92+
let running_services = RunningServices::parse_from_logs(logs);
93+
94+
assert_eq!(running_services.udp_trackers, vec!["127.0.0.1:8080"]);
95+
assert_eq!(running_services.http_trackers, vec!["127.0.0.1:9090"]);
96+
assert_eq!(running_services.health_checks, vec!["127.0.0.1:10010/health_check"]);
97+
}
98+
99+
#[test]
100+
fn it_should_ignore_logs_with_no_matching_lines() {
101+
let logs = "[Other Service][INFO] Starting on: 0.0.0.0:7070";
102+
let running_services = RunningServices::parse_from_logs(logs);
103+
104+
assert!(running_services.udp_trackers.is_empty());
105+
assert!(running_services.http_trackers.is_empty());
106+
assert!(running_services.health_checks.is_empty());
107+
}
108+
109+
#[test]
110+
fn it_should_replace_wildcard_ip_with_localhost() {
111+
let address = "0.0.0.0:8080";
112+
assert_eq!(RunningServices::replace_wildcard_ip_with_localhost(address), "127.0.0.1:8080");
113+
}
114+
}

src/e2e/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
pub mod docker;
2-
pub mod logs;
2+
pub mod logs_parser;
33
pub mod runner;
44
pub mod temp_dir;

src/e2e/runner.rs

+89-66
Original file line numberDiff line numberDiff line change
@@ -9,102 +9,68 @@ use log::{debug, info, LevelFilter};
99
use rand::distributions::Alphanumeric;
1010
use rand::Rng;
1111

12+
use super::docker::RunningContainer;
1213
use crate::e2e::docker::Docker;
13-
use crate::e2e::logs::RunningServices;
14+
use crate::e2e::logs_parser::RunningServices;
1415
use crate::e2e::temp_dir::Handler;
1516

1617
pub const NUMBER_OF_ARGUMENTS: usize = 2;
18+
const CONTAINER_TAG: &str = "torrust-tracker:local";
19+
const TRACKER_CHECKER_CONFIG_FILE: &str = "tracker_checker.json";
1720

21+
pub struct Arguments {
22+
pub tracker_config_path: String,
23+
}
24+
25+
/// Script to run E2E tests.
26+
///
1827
/// # Panics
1928
///
2029
/// Will panic if it can't not perform any of the operations.
2130
pub fn run() {
22-
// todo: const
23-
let container_tag: &str = "torrust-tracker:local";
24-
let tracker_checker_config_file = "tracker_checker.json";
25-
26-
setup_logging(LevelFilter::Info);
31+
setup_runner_logging(LevelFilter::Info);
2732

2833
let args = parse_arguments();
2934

30-
// Setup tracker configuration
31-
info!("Reading tracker configuration from file: {} ...", args.tracker_config_path);
32-
let tracker_config = read_tracker_config(&args.tracker_config_path);
35+
let tracker_config = load_tracker_configuration(&args.tracker_config_path);
3336

34-
// Build tracker container image
35-
Docker::build("./Containerfile", container_tag).expect("A tracker local docker image should be built");
37+
build_tracker_container_image(CONTAINER_TAG);
3638

37-
// Create temp dir
38-
info!(
39-
"Current dir: {:?}",
40-
env::current_dir().expect("It should return the current dir")
41-
);
42-
let temp_dir_handler = Handler::new().expect("A temp dir should be created");
43-
info!("Temp dir created: {:?}", temp_dir_handler.temp_dir);
39+
let temp_dir = create_temp_dir();
4440

45-
// Run the tracker container
4641
let container_name = generate_random_container_name("tracker_");
4742

4843
// code-review: if we want to use port 0 we don't know which ports we have to open.
4944
// Besides, if we don't use port 0 we should get the port numbers from the tracker configuration.
5045
// We could not use docker, but the intention was to create E2E tests including containerization.
51-
info!("Running docker tracker image: {container_name} ...");
5246
let env_vars = [("TORRUST_TRACKER_CONFIG".to_string(), tracker_config.to_string())];
5347
let ports = [
5448
"6969:6969/udp".to_string(),
5549
"7070:7070/tcp".to_string(),
5650
"1212:1212/tcp".to_string(),
5751
"1313:1313/tcp".to_string(),
5852
];
59-
let container =
60-
Docker::run(container_tag, &container_name, &env_vars, &ports).expect("A tracker local docker image should be running");
61-
62-
info!("Waiting for the container {container_name} to be healthy ...");
63-
let is_healthy = Docker::wait_until_is_healthy(&container_name, Duration::from_secs(10));
64-
65-
assert!(is_healthy, "Unhealthy tracker container: {container_name}");
6653

67-
info!("Container {container_name} is healthy ...");
54+
let container = run_tracker_container(&container_name, &env_vars, &ports);
6855

69-
// Extract running services from container logs
56+
let running_services = parse_running_services_from_logs(&container);
7057

71-
let logs = Docker::logs(&container_name).expect("Logs should be captured from running container");
72-
73-
debug!("Logs after starting the container:\n{logs}");
58+
let tracker_checker_config =
59+
serde_json::to_string_pretty(&running_services).expect("Running services should be serialized into JSON");
7460

75-
let mut config = RunningServices::default();
76-
config.extract_from_logs(&logs);
61+
let mut tracker_checker_config_path = PathBuf::from(&temp_dir.temp_dir.path());
62+
tracker_checker_config_path.push(TRACKER_CHECKER_CONFIG_FILE);
7763

78-
let json = serde_json::to_string_pretty(&config).expect("Running services should be serialized into JSON");
64+
write_tracker_checker_config(&tracker_checker_config_path, &tracker_checker_config);
7965

80-
// Write tracker_checker configuration file
81-
82-
let mut absolute_tracker_checker_config_path = PathBuf::from(&temp_dir_handler.temp_dir.path());
83-
absolute_tracker_checker_config_path.push(tracker_checker_config_file);
84-
85-
let mut file = File::create(absolute_tracker_checker_config_path.clone()).expect("Tracker checker config file to be created");
86-
file.write_all(json.as_bytes())
87-
.expect("Tracker checker config file to be written");
88-
info!(
89-
"Tracker checker configuration file: {:?} \n{json}",
90-
absolute_tracker_checker_config_path
91-
);
92-
93-
// Run the tracker_checker
94-
95-
info!(
96-
"Running tacker checker: cargo --bin tracker_checker {}",
97-
absolute_tracker_checker_config_path.display()
98-
);
99-
100-
run_tracker_checker(&absolute_tracker_checker_config_path).expect("Tracker checker should check running services");
101-
102-
// End: container will be removed automatically when the `RunningContainer` is dropped.
66+
run_tracker_checker(&tracker_checker_config_path).expect("Tracker checker should check running services");
10367

10468
info!("Running container `{}` will be automatically removed", container.name);
69+
70+
// Container will be removed automatically when the `RunningContainer` is dropped.
10571
}
10672

107-
fn setup_logging(level: LevelFilter) {
73+
fn setup_runner_logging(level: LevelFilter) {
10874
if let Err(_err) = fern::Dispatch::new()
10975
.format(|out, message, record| {
11076
out.finish(format_args!(
@@ -125,10 +91,6 @@ fn setup_logging(level: LevelFilter) {
12591
debug!("logging initialized.");
12692
}
12793

128-
pub struct Arguments {
129-
pub tracker_config_path: String,
130-
}
131-
13294
fn parse_arguments() -> Arguments {
13395
let args: Vec<String> = std::env::args().collect();
13496

@@ -145,9 +107,31 @@ fn parse_arguments() -> Arguments {
145107
}
146108
}
147109

148-
fn read_tracker_config(tracker_config_path: &str) -> String {
149-
std::fs::read_to_string(tracker_config_path)
150-
.unwrap_or_else(|_| panic!("Can't read tracker config file {tracker_config_path}"))
110+
fn load_tracker_configuration(tracker_config_path: &str) -> String {
111+
info!("Reading tracker configuration from file: {} ...", tracker_config_path);
112+
read_file(tracker_config_path)
113+
}
114+
115+
fn read_file(path: &str) -> String {
116+
std::fs::read_to_string(path).unwrap_or_else(|_| panic!("Can't read file {path}"))
117+
}
118+
119+
fn build_tracker_container_image(tag: &str) {
120+
info!("Building tracker container image with tag: {} ...", tag);
121+
Docker::build("./Containerfile", tag).expect("A tracker local docker image should be built");
122+
}
123+
124+
fn create_temp_dir() -> Handler {
125+
debug!(
126+
"Current dir: {:?}",
127+
env::current_dir().expect("It should return the current dir")
128+
);
129+
130+
let temp_dir_handler = Handler::new().expect("A temp dir should be created");
131+
132+
info!("Temp dir created: {:?}", temp_dir_handler.temp_dir);
133+
134+
temp_dir_handler
151135
}
152136

153137
fn generate_random_container_name(prefix: &str) -> String {
@@ -160,6 +144,40 @@ fn generate_random_container_name(prefix: &str) -> String {
160144
format!("{prefix}{rand_string}")
161145
}
162146

147+
fn run_tracker_container(container_name: &str, env_vars: &[(String, String)], ports: &[String]) -> RunningContainer {
148+
info!("Running docker tracker image: {container_name} ...");
149+
150+
let container =
151+
Docker::run(CONTAINER_TAG, container_name, env_vars, ports).expect("A tracker local docker image should be running");
152+
153+
info!("Waiting for the container {container_name} to be healthy ...");
154+
155+
let is_healthy = Docker::wait_until_is_healthy(container_name, Duration::from_secs(10));
156+
157+
assert!(is_healthy, "Unhealthy tracker container: {container_name}");
158+
159+
debug!("Container {container_name} is healthy ...");
160+
161+
container
162+
}
163+
164+
fn parse_running_services_from_logs(container: &RunningContainer) -> RunningServices {
165+
let logs = Docker::logs(&container.name).expect("Logs should be captured from running container");
166+
167+
debug!("Logs after starting the container:\n{logs}");
168+
169+
RunningServices::parse_from_logs(&logs)
170+
}
171+
172+
fn write_tracker_checker_config(config_file_path: &Path, config: &str) {
173+
let mut file = File::create(config_file_path).expect("Tracker checker config file to be created");
174+
175+
file.write_all(config.as_bytes())
176+
.expect("Tracker checker config file to be written");
177+
178+
info!("Tracker checker configuration file: {:?} \n{config}", config_file_path);
179+
}
180+
163181
/// Runs the tracker checker
164182
///
165183
/// ```text
@@ -174,6 +192,11 @@ fn generate_random_container_name(prefix: &str) -> String {
174192
///
175193
/// Will panic if the config path is not a valid string.
176194
pub fn run_tracker_checker(config_path: &Path) -> io::Result<()> {
195+
info!(
196+
"Running tacker checker: cargo --bin tracker_checker {}",
197+
config_path.display()
198+
);
199+
177200
let path = config_path.to_str().expect("The path should be a valid string");
178201

179202
let status = Command::new("cargo")

0 commit comments

Comments
 (0)