Skip to content

Commit 815f2ca

Browse files
committed
ci: [torrust#634] E2E test runner: parse log to get running services
1 parent 6398dc1 commit 815f2ca

File tree

4 files changed

+162
-21
lines changed

4 files changed

+162
-21
lines changed

src/e2e/docker.rs

+53-18
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::process::{Child, Command, Output, Stdio};
33
use std::thread::sleep;
44
use std::time::{Duration, Instant};
55

6+
use log::debug;
7+
68
pub struct Docker {}
79

810
impl Docker {
@@ -19,24 +21,51 @@ impl Docker {
1921
if status.success() {
2022
Ok(())
2123
} else {
22-
Err(io::Error::new(io::ErrorKind::Other, "Failed to build Docker image"))
24+
Err(io::Error::new(
25+
io::ErrorKind::Other,
26+
format!("Failed to build Docker image from dockerfile {dockerfile}"),
27+
))
2328
}
2429
}
2530

26-
/// Runs a Docker container from a given image.
31+
/// Runs a Docker container from a given image with multiple environment variables.
32+
///
33+
/// # Arguments
34+
///
35+
/// * `image` - The Docker image to run.
36+
/// * `container` - The name for the Docker container.
37+
/// * `env_vars` - A slice of tuples, each representing an environment variable as ("KEY", "value").
2738
///
2839
/// # Errors
2940
///
3041
/// Will fail if the docker run command fails.
31-
pub fn run(image: &str, name: &str) -> io::Result<Output> {
32-
let output = Command::new("docker")
33-
.args(["run", "--detach", "--name", name, image])
34-
.output()?;
42+
pub fn run(image: &str, container: &str, env_vars: &[(String, String)]) -> io::Result<Output> {
43+
let initial_args = vec![
44+
"run".to_string(),
45+
"--detach".to_string(),
46+
"--name".to_string(),
47+
container.to_string(),
48+
];
49+
50+
let mut env_var_args: Vec<String> = vec![];
51+
for (key, value) in env_vars {
52+
env_var_args.push("--env".to_string());
53+
env_var_args.push(format!("{key}={value}"));
54+
}
55+
56+
let args = [initial_args, env_var_args, [image.to_string()].to_vec()].concat();
57+
58+
debug!("Docker run args: {:?}", args);
59+
60+
let output = Command::new("docker").args(args).output()?;
3561

3662
if output.status.success() {
3763
Ok(output)
3864
} else {
39-
Err(io::Error::new(io::ErrorKind::Other, "Failed to run Docker container"))
65+
Err(io::Error::new(
66+
io::ErrorKind::Other,
67+
format!("Failed to run Docker image {image}"),
68+
))
4069
}
4170
}
4271

@@ -45,9 +74,9 @@ impl Docker {
4574
/// # Errors
4675
///
4776
/// Will fail if the docker run command fails to start.
48-
pub fn run_spawned(image: &str, name: &str) -> io::Result<Child> {
77+
pub fn run_spawned(image: &str, container: &str) -> io::Result<Child> {
4978
let child = Command::new("docker")
50-
.args(["run", "--name", name, image])
79+
.args(["run", "--name", container, image])
5180
.stdin(Stdio::null()) // Ignore stdin
5281
.stdout(Stdio::null()) // Ignore stdout
5382
.stderr(Stdio::null()) // Ignore stderr
@@ -61,13 +90,16 @@ impl Docker {
6190
/// # Errors
6291
///
6392
/// Will fail if the docker stop command fails.
64-
pub fn stop(name: &str) -> io::Result<()> {
65-
let status = Command::new("docker").args(["stop", name]).status()?;
93+
pub fn stop(container: &str) -> io::Result<()> {
94+
let status = Command::new("docker").args(["stop", container]).status()?;
6695

6796
if status.success() {
6897
Ok(())
6998
} else {
70-
Err(io::Error::new(io::ErrorKind::Other, "Failed to stop Docker container"))
99+
Err(io::Error::new(
100+
io::ErrorKind::Other,
101+
format!("Failed to stop Docker container {container}"),
102+
))
71103
}
72104
}
73105

@@ -76,13 +108,16 @@ impl Docker {
76108
/// # Errors
77109
///
78110
/// Will fail if the docker rm command fails.
79-
pub fn remove(name: &str) -> io::Result<()> {
80-
let status = Command::new("docker").args(["rm", "-f", name]).status()?;
111+
pub fn remove(container: &str) -> io::Result<()> {
112+
let status = Command::new("docker").args(["rm", "-f", container]).status()?;
81113

82114
if status.success() {
83115
Ok(())
84116
} else {
85-
Err(io::Error::new(io::ErrorKind::Other, "Failed to remove Docker container"))
117+
Err(io::Error::new(
118+
io::ErrorKind::Other,
119+
format!("Failed to remove Docker container {container}"),
120+
))
86121
}
87122
}
88123

@@ -91,15 +126,15 @@ impl Docker {
91126
/// # Errors
92127
///
93128
/// Will fail if the docker logs command fails.
94-
pub fn logs(container_name: &str) -> io::Result<String> {
95-
let output = Command::new("docker").args(["logs", container_name]).output()?;
129+
pub fn logs(container: &str) -> io::Result<String> {
130+
let output = Command::new("docker").args(["logs", container]).output()?;
96131

97132
if output.status.success() {
98133
Ok(String::from_utf8_lossy(&output.stdout).to_string())
99134
} else {
100135
Err(io::Error::new(
101136
io::ErrorKind::Other,
102-
"Failed to fetch logs from Docker container",
137+
format!("Failed to fetch logs from Docker container {container}"),
103138
))
104139
}
105140
}

src/e2e/logs.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
#[derive(Serialize, Deserialize, Debug, Default)]
4+
pub struct RunningServices {
5+
pub udp_trackers: Vec<String>,
6+
pub http_trackers: Vec<String>,
7+
pub health_checks: Vec<String>,
8+
}
9+
10+
impl RunningServices {
11+
pub fn extract_from_logs(&mut self, logs: &str) {
12+
// todo: extract duplicate code
13+
for line in logs.lines() {
14+
let heal_check_pattern = "[Health Check API][INFO] Starting on: ";
15+
let udp_tracker_pattern = "[UDP Tracker][INFO] Starting on: udp://";
16+
let http_tracker_pattern = "[HTTP Tracker][INFO] Starting on: ";
17+
18+
if let Some(start) = line.find(heal_check_pattern) {
19+
let address = &line[start + heal_check_pattern.len()..].trim();
20+
self.health_checks.push(Self::replace_wildcard_ip_with_localhost(address));
21+
} else if let Some(start) = line.find(udp_tracker_pattern) {
22+
let address = &line[start + udp_tracker_pattern.len()..].trim();
23+
self.udp_trackers.push(Self::replace_wildcard_ip_with_localhost(address));
24+
} else if let Some(start) = line.find(http_tracker_pattern) {
25+
let address = &line[start + http_tracker_pattern.len()..].trim();
26+
self.http_trackers.push(Self::replace_wildcard_ip_with_localhost(address));
27+
}
28+
}
29+
}
30+
31+
fn replace_wildcard_ip_with_localhost(address: &str) -> String {
32+
address.replace("0.0.0.0", "127.0.0.1")
33+
}
34+
}

src/e2e/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod docker;
2+
pub mod logs;
23
pub mod runner;
34
pub mod temp_dir;

src/e2e/runner.rs

+74-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
use std::env;
22
use std::time::Duration;
33

4+
use log::{debug, LevelFilter};
45
use rand::distributions::Alphanumeric;
56
use rand::Rng;
67

78
use crate::e2e::docker::Docker;
9+
use crate::e2e::logs::RunningServices;
810
use crate::e2e::temp_dir::Handler;
911

12+
pub const NUMBER_OF_ARGUMENTS: usize = 2;
13+
1014
/// # Panics
1115
///
1216
/// Will panic if:
@@ -21,14 +25,24 @@ pub fn run() {
2125
- [x] Build the docker image.
2226
- [x] Run the docker image.
2327
- [x] Wait until the container is healthy.
24-
- [ ] Parse logs to get running services.
28+
- [x] Parse logs to get running services.
2529
- [ ] Build config file for the tracker_checker.
2630
- [ ] Run the tracker_checker.
2731
- [x] Stop the container.
2832
2933
*/
3034

31-
Docker::build("./Containerfile", "local").expect("A tracker local docker image should be built");
35+
setup_logging(LevelFilter::Debug);
36+
37+
let args = parse_arguments();
38+
39+
println!("Reading tracker configuration from file: {} ...", args.tracker_config_path);
40+
41+
let tracker_config = read_tracker_config(&args.tracker_config_path);
42+
43+
let container_tag = "torrust-tracker:local";
44+
45+
Docker::build("./Containerfile", container_tag).expect("A tracker local docker image should be built");
3246

3347
println!(
3448
"Current dir: {:?}",
@@ -47,7 +61,8 @@ pub fn run() {
4761
let container_name = generate_random_container_name("tracker_");
4862

4963
println!("Running docker tracker image: {container_name} ...");
50-
Docker::run("local", &container_name).expect("A tracker local docker image should be running");
64+
let env_vars = [("TORRUST_TRACKER_CONFIG".to_string(), tracker_config.to_string())];
65+
Docker::run(container_tag, &container_name, &env_vars).expect("A tracker local docker image should be running");
5166

5267
println!("Waiting for the container {container_name} to be healthy ...");
5368
let is_healthy = Docker::wait_until_is_healthy(&container_name, Duration::from_secs(10));
@@ -61,6 +76,16 @@ pub fn run() {
6176

6277
println!("Container {container_name} is healthy ...");
6378

79+
let logs = Docker::logs(&container_name).expect("Logs should be captured from running container");
80+
81+
debug!("Logs after starting the container: {logs}");
82+
83+
let mut config = RunningServices::default();
84+
config.extract_from_logs(&logs);
85+
86+
let json = serde_json::to_string_pretty(&config).unwrap();
87+
println!("Tracker checker configuration: {json}");
88+
6489
println!("Stopping docker tracker image: {container_name} ...");
6590
Docker::stop(&container_name).expect("A tracker local docker image should be stopped");
6691

@@ -70,6 +95,52 @@ pub fn run() {
7095
.expect("The app should revert dir from temp dir to the original one");
7196
}
7297

98+
fn setup_logging(level: LevelFilter) {
99+
if let Err(_err) = fern::Dispatch::new()
100+
.format(|out, message, record| {
101+
out.finish(format_args!(
102+
"{} [{}][{}] {}",
103+
chrono::Local::now().format("%+"),
104+
record.target(),
105+
record.level(),
106+
message
107+
));
108+
})
109+
.level(level)
110+
.chain(std::io::stdout())
111+
.apply()
112+
{
113+
panic!("Failed to initialize logging.")
114+
}
115+
116+
debug!("logging initialized.");
117+
}
118+
119+
pub struct Arguments {
120+
pub tracker_config_path: String,
121+
}
122+
123+
fn parse_arguments() -> Arguments {
124+
let args: Vec<String> = std::env::args().collect();
125+
126+
if args.len() < NUMBER_OF_ARGUMENTS {
127+
eprintln!("Usage: cargo run --bin e2e_tests_runner <PATH_TO_TRACKER_CONFIG_FILE>");
128+
eprintln!("For example: cargo run --bin e2e_tests_runner ./share/default/config/tracker.e2e.container.sqlite3.toml");
129+
std::process::exit(1);
130+
}
131+
132+
let config_path = &args[1];
133+
134+
Arguments {
135+
tracker_config_path: config_path.to_string(),
136+
}
137+
}
138+
139+
fn read_tracker_config(tracker_config_path: &str) -> String {
140+
std::fs::read_to_string(tracker_config_path)
141+
.unwrap_or_else(|_| panic!("Can't read tracker config file {tracker_config_path}"))
142+
}
143+
73144
fn generate_random_container_name(prefix: &str) -> String {
74145
let rand_string: String = rand::thread_rng()
75146
.sample_iter(&Alphanumeric)

0 commit comments

Comments
 (0)