-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ci: [#634] new script to run E2E tests
It uses Rust instead of Bash. You can run it with: ``` cargo run --bin e2e_tests_runner share/default/config/tracker.e2e.container.sqlite3.toml ``` It will: - Build the tracker docker image. - Run the docker image. - Wait until the container is healthy. - Parse logs to get running services. - Build config file for the tracker_checker. - Run the tracker_checker. - Stop the container.
- Loading branch information
1 parent
8e43205
commit 4edcd2e
Showing
8 changed files
with
614 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
announce_interval = 120 | ||
db_driver = "Sqlite3" | ||
db_path = "/var/lib/torrust/tracker/database/sqlite3.db" | ||
external_ip = "0.0.0.0" | ||
inactive_peer_cleanup_interval = 600 | ||
log_level = "info" | ||
max_peer_timeout = 900 | ||
min_announce_interval = 120 | ||
mode = "public" | ||
on_reverse_proxy = false | ||
persistent_torrent_completed_stat = false | ||
remove_peerless_torrents = true | ||
tracker_usage_statistics = true | ||
|
||
[[udp_trackers]] | ||
bind_address = "0.0.0.0:6969" | ||
enabled = true | ||
|
||
[[http_trackers]] | ||
bind_address = "0.0.0.0:7070" | ||
enabled = true | ||
ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" | ||
ssl_enabled = false | ||
ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" | ||
|
||
[http_api] | ||
bind_address = "0.0.0.0:1212" | ||
enabled = true | ||
ssl_cert_path = "/var/lib/torrust/tracker/tls/localhost.crt" | ||
ssl_enabled = false | ||
ssl_key_path = "/var/lib/torrust/tracker/tls/localhost.key" | ||
|
||
# Please override the admin token setting the | ||
# `TORRUST_TRACKER_API_ADMIN_TOKEN` | ||
# environmental variable! | ||
|
||
[http_api.access_tokens] | ||
admin = "MyAccessToken" | ||
|
||
[health_check_api] | ||
bind_address = "0.0.0.0:1313" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
//! Program to run E2E tests. | ||
//! | ||
//! ```text | ||
//! cargo run --bin e2e_tests_runner share/default/config/tracker.e2e.container.sqlite3.toml | ||
//! ``` | ||
use torrust_tracker::e2e; | ||
|
||
fn main() { | ||
e2e::runner::run(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
//! Docker command wrapper. | ||
use std::io; | ||
use std::process::{Command, Output}; | ||
use std::thread::sleep; | ||
use std::time::{Duration, Instant}; | ||
|
||
use log::debug; | ||
|
||
/// Docker command wrapper. | ||
pub struct Docker {} | ||
|
||
pub struct RunningContainer { | ||
pub name: String, | ||
pub output: Output, | ||
} | ||
|
||
impl Drop for RunningContainer { | ||
/// Ensures that the temporary container is stopped and removed when the | ||
/// struct goes out of scope. | ||
fn drop(&mut self) { | ||
let _unused = Docker::stop(self); | ||
let _unused = Docker::remove(&self.name); | ||
} | ||
} | ||
|
||
impl Docker { | ||
/// Builds a Docker image from a given Dockerfile. | ||
/// | ||
/// # Errors | ||
/// | ||
/// Will fail if the docker build command fails. | ||
pub fn build(dockerfile: &str, tag: &str) -> io::Result<()> { | ||
let status = Command::new("docker") | ||
.args(["build", "-f", dockerfile, "-t", tag, "."]) | ||
.status()?; | ||
|
||
if status.success() { | ||
Ok(()) | ||
} else { | ||
Err(io::Error::new( | ||
io::ErrorKind::Other, | ||
format!("Failed to build Docker image from dockerfile {dockerfile}"), | ||
)) | ||
} | ||
} | ||
|
||
/// Runs a Docker container from a given image with multiple environment variables. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `image` - The Docker image to run. | ||
/// * `container` - The name for the Docker container. | ||
/// * `env_vars` - A slice of tuples, each representing an environment variable as ("KEY", "value"). | ||
/// | ||
/// # Errors | ||
/// | ||
/// Will fail if the docker run command fails. | ||
pub fn run(image: &str, container: &str, env_vars: &[(String, String)], ports: &[String]) -> io::Result<RunningContainer> { | ||
let initial_args = vec![ | ||
"run".to_string(), | ||
"--detach".to_string(), | ||
"--name".to_string(), | ||
container.to_string(), | ||
]; | ||
|
||
// Add environment variables | ||
let mut env_var_args: Vec<String> = vec![]; | ||
for (key, value) in env_vars { | ||
env_var_args.push("--env".to_string()); | ||
env_var_args.push(format!("{key}={value}")); | ||
} | ||
|
||
// Add port mappings | ||
let mut port_args: Vec<String> = vec![]; | ||
for port in ports { | ||
port_args.push("--publish".to_string()); | ||
port_args.push(port.to_string()); | ||
} | ||
|
||
let args = [initial_args, env_var_args, port_args, [image.to_string()].to_vec()].concat(); | ||
|
||
debug!("Docker run args: {:?}", args); | ||
|
||
let output = Command::new("docker").args(args).output()?; | ||
|
||
if output.status.success() { | ||
Ok(RunningContainer { | ||
name: container.to_owned(), | ||
output, | ||
}) | ||
} else { | ||
Err(io::Error::new( | ||
io::ErrorKind::Other, | ||
format!("Failed to run Docker image {image}"), | ||
)) | ||
} | ||
} | ||
|
||
/// Stops a Docker container. | ||
/// | ||
/// # Errors | ||
/// | ||
/// Will fail if the docker stop command fails. | ||
pub fn stop(container: &RunningContainer) -> io::Result<()> { | ||
let status = Command::new("docker").args(["stop", &container.name]).status()?; | ||
|
||
if status.success() { | ||
Ok(()) | ||
} else { | ||
Err(io::Error::new( | ||
io::ErrorKind::Other, | ||
format!("Failed to stop Docker container {}", container.name), | ||
)) | ||
} | ||
} | ||
|
||
/// Removes a Docker container. | ||
/// | ||
/// # Errors | ||
/// | ||
/// Will fail if the docker rm command fails. | ||
pub fn remove(container: &str) -> io::Result<()> { | ||
let status = Command::new("docker").args(["rm", "-f", container]).status()?; | ||
|
||
if status.success() { | ||
Ok(()) | ||
} else { | ||
Err(io::Error::new( | ||
io::ErrorKind::Other, | ||
format!("Failed to remove Docker container {container}"), | ||
)) | ||
} | ||
} | ||
|
||
/// Fetches logs from a Docker container. | ||
/// | ||
/// # Errors | ||
/// | ||
/// Will fail if the docker logs command fails. | ||
pub fn logs(container: &str) -> io::Result<String> { | ||
let output = Command::new("docker").args(["logs", container]).output()?; | ||
|
||
if output.status.success() { | ||
Ok(String::from_utf8_lossy(&output.stdout).to_string()) | ||
} else { | ||
Err(io::Error::new( | ||
io::ErrorKind::Other, | ||
format!("Failed to fetch logs from Docker container {container}"), | ||
)) | ||
} | ||
} | ||
|
||
/// Checks if a Docker container is healthy. | ||
#[must_use] | ||
pub fn wait_until_is_healthy(name: &str, timeout: Duration) -> bool { | ||
let start = Instant::now(); | ||
|
||
while start.elapsed() < timeout { | ||
let Ok(output) = Command::new("docker") | ||
.args(["ps", "-f", &format!("name={name}"), "--format", "{{.Status}}"]) | ||
.output() | ||
else { | ||
return false; | ||
}; | ||
|
||
let output_str = String::from_utf8_lossy(&output.stdout); | ||
|
||
if output_str.contains("(healthy)") { | ||
return true; | ||
} | ||
|
||
sleep(Duration::from_secs(1)); | ||
} | ||
|
||
false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
//! Utilities to parse Torrust Tracker logs. | ||
use serde::{Deserialize, Serialize}; | ||
|
||
const UDP_TRACKER_PATTERN: &str = "[UDP Tracker][INFO] Starting on: udp://"; | ||
const HTTP_TRACKER_PATTERN: &str = "[HTTP Tracker][INFO] Starting on: "; | ||
const HEALTH_CHECK_PATTERN: &str = "[Health Check API][INFO] Starting on: "; | ||
|
||
#[derive(Serialize, Deserialize, Debug, Default)] | ||
pub struct RunningServices { | ||
pub udp_trackers: Vec<String>, | ||
pub http_trackers: Vec<String>, | ||
pub health_checks: Vec<String>, | ||
} | ||
|
||
impl RunningServices { | ||
/// It parses the tracker logs to extract the running services. | ||
/// | ||
/// For example, from this logs: | ||
/// | ||
/// ```text | ||
/// Loading default configuration file: `./share/default/config/tracker.development.sqlite3.toml` ... | ||
/// 2024-01-24T16:36:14.614898789+00:00 [torrust_tracker::bootstrap::logging][INFO] logging initialized. | ||
/// 2024-01-24T16:36:14.615586025+00:00 [UDP Tracker][INFO] Starting on: udp://0.0.0.0:6969 | ||
/// 2024-01-24T16:36:14.615623705+00:00 [torrust_tracker::bootstrap::jobs][INFO] TLS not enabled | ||
/// 2024-01-24T16:36:14.615694484+00:00 [HTTP Tracker][INFO] Starting on: http://0.0.0.0:7070 | ||
/// 2024-01-24T16:36:14.615710534+00:00 [HTTP Tracker][INFO] Started on: http://0.0.0.0:7070 | ||
/// 2024-01-24T16:36:14.615716574+00:00 [torrust_tracker::bootstrap::jobs][INFO] TLS not enabled | ||
/// 2024-01-24T16:36:14.615764904+00:00 [API][INFO] Starting on http://127.0.0.1:1212 | ||
/// 2024-01-24T16:36:14.615767264+00:00 [API][INFO] Started on http://127.0.0.1:1212 | ||
/// 2024-01-24T16:36:14.615777574+00:00 [Health Check API][INFO] Starting on: http://127.0.0.1:1313 | ||
/// 2024-01-24T16:36:14.615791124+00:00 [Health Check API][INFO] Started on: http://127.0.0.1:1313 | ||
/// ``` | ||
/// | ||
/// It would extract these services: | ||
/// | ||
/// ```json | ||
/// { | ||
/// "udp_trackers": [ | ||
/// "127.0.0.1:6969" | ||
/// ], | ||
/// "http_trackers": [ | ||
/// "http://127.0.0.1:7070" | ||
/// ], | ||
/// "health_checks": [ | ||
/// "http://127.0.0.1:1313/health_check" | ||
/// ] | ||
/// } | ||
/// ``` | ||
#[must_use] | ||
pub fn parse_from_logs(logs: &str) -> Self { | ||
let mut udp_trackers: Vec<String> = Vec::new(); | ||
let mut http_trackers: Vec<String> = Vec::new(); | ||
let mut health_checks: Vec<String> = Vec::new(); | ||
|
||
for line in logs.lines() { | ||
if let Some(address) = Self::extract_address_if_matches(line, UDP_TRACKER_PATTERN) { | ||
udp_trackers.push(address); | ||
} else if let Some(address) = Self::extract_address_if_matches(line, HTTP_TRACKER_PATTERN) { | ||
http_trackers.push(address); | ||
} else if let Some(address) = Self::extract_address_if_matches(line, HEALTH_CHECK_PATTERN) { | ||
health_checks.push(format!("{address}/health_check")); | ||
} | ||
} | ||
|
||
Self { | ||
udp_trackers, | ||
http_trackers, | ||
health_checks, | ||
} | ||
} | ||
|
||
fn extract_address_if_matches(line: &str, pattern: &str) -> Option<String> { | ||
line.find(pattern) | ||
.map(|start| Self::replace_wildcard_ip_with_localhost(line[start + pattern.len()..].trim())) | ||
} | ||
|
||
fn replace_wildcard_ip_with_localhost(address: &str) -> String { | ||
address.replace("0.0.0.0", "127.0.0.1") | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn it_should_parse_from_logs_with_valid_logs() { | ||
let logs = "\ | ||
[UDP Tracker][INFO] Starting on: udp://0.0.0.0:8080\n\ | ||
[HTTP Tracker][INFO] Starting on: 0.0.0.0:9090\n\ | ||
[Health Check API][INFO] Starting on: 0.0.0.0:10010"; | ||
let running_services = RunningServices::parse_from_logs(logs); | ||
|
||
assert_eq!(running_services.udp_trackers, vec!["127.0.0.1:8080"]); | ||
assert_eq!(running_services.http_trackers, vec!["127.0.0.1:9090"]); | ||
assert_eq!(running_services.health_checks, vec!["127.0.0.1:10010/health_check"]); | ||
} | ||
|
||
#[test] | ||
fn it_should_ignore_logs_with_no_matching_lines() { | ||
let logs = "[Other Service][INFO] Starting on: 0.0.0.0:7070"; | ||
let running_services = RunningServices::parse_from_logs(logs); | ||
|
||
assert!(running_services.udp_trackers.is_empty()); | ||
assert!(running_services.http_trackers.is_empty()); | ||
assert!(running_services.health_checks.is_empty()); | ||
} | ||
|
||
#[test] | ||
fn it_should_replace_wildcard_ip_with_localhost() { | ||
let address = "0.0.0.0:8080"; | ||
assert_eq!(RunningServices::replace_wildcard_ip_with_localhost(address), "127.0.0.1:8080"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
pub mod docker; | ||
pub mod logs_parser; | ||
pub mod runner; | ||
pub mod temp_dir; |
Oops, something went wrong.