Skip to content

Commit

Permalink
dev: rework error for torrent tracker
Browse files Browse the repository at this point in the history
  • Loading branch information
da2ce7 committed Mar 28, 2024
1 parent bf71687 commit 2933092
Show file tree
Hide file tree
Showing 15 changed files with 326 additions and 184 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@
"evenBetterToml.formatter.trailingNewline": true,
"evenBetterToml.formatter.reorderKeys": true,
"evenBetterToml.formatter.reorderArrays": true,
"rust-analyzer.showUnlinkedFileNotification": false,

}
12 changes: 0 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ torrust-tracker-located-error = { version = "3.0.0-alpha.12-develop", path = "pa
torrust-tracker-primitives = { version = "3.0.0-alpha.12-develop", path = "packages/primitives" }
torrust-tracker-torrent-repository = { version = "3.0.0-alpha.12-develop", path = "packages/torrent-repository" }
tower-http = { version = "0", features = ["compression-full", "cors", "propagate-header", "request-id", "trace"] }
trace = "0"
tracing = "0"
url = "2"
uuid = { version = "1", features = ["v4"] }
Expand Down
3 changes: 2 additions & 1 deletion cSpell.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"certbot",
"chrono",
"clippy",
"Cloneable",
"codecov",
"codegen",
"completei",
Expand Down Expand Up @@ -69,8 +70,8 @@
"infoschema",
"Intermodal",
"intervali",
"kcachegrind",
"Joakim",
"kcachegrind",
"keyout",
"lcov",
"leecher",
Expand Down
24 changes: 13 additions & 11 deletions src/console/clients/checker/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub async fn run() -> Result<Vec<CheckResult>> {
console: console_printer,
};

Ok(service.run_checks().await)
service.run_checks().await
}

fn setup_logging(level: LevelFilter) {
Expand All @@ -78,15 +78,17 @@ fn setup_logging(level: LevelFilter) {
}

fn setup_config(args: Args) -> Result<Configuration> {
match (args.config_path, args.config_content) {
(Some(config_path), _) => load_config_from_file(&config_path),
(_, Some(config_content)) => parse_from_json(&config_content).context("invalid config format"),
_ => Err(anyhow::anyhow!("no configuration provided")),
// If a config is directly supplied, we use it.
if let Some(config) = args.config_content {
parse_from_json(&config).context("invalid config format")
}

Check warning on line 84 in src/console/clients/checker/app.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/app.rs#L82-L84

Added lines #L82 - L84 were not covered by tests
// or we load it from a file...
else if let Some(path) = args.config_path {
let file_content = std::fs::read_to_string(path.clone()).with_context(|| format!("can't read config file {path:?}"))?;
parse_from_json(&file_content).context("invalid config format")
}

Check warning on line 89 in src/console/clients/checker/app.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/app.rs#L86-L89

Added lines #L86 - L89 were not covered by tests
// but we cannot run without any config...
else {
Err(anyhow::anyhow!("no configuration provided"))

Check warning on line 92 in src/console/clients/checker/app.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/app.rs#L92

Added line #L92 was not covered by tests
}
}

fn load_config_from_file(path: &PathBuf) -> Result<Configuration> {
let file_content = std::fs::read_to_string(path).with_context(|| format!("can't read config file {path:?}"))?;

parse_from_json(&file_content).context("invalid config format")
}
90 changes: 57 additions & 33 deletions src/console/clients/checker/checks/health.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,75 @@
use std::sync::Arc;
use std::time::Duration;

use anyhow::Result;
use colored::Colorize;
use reqwest::{Client as HttpClient, Url, Url as ServiceUrl};
use hyper::StatusCode;
use reqwest::{Client as HttpClient, Response};
use thiserror::Error;
use url::Url;

use crate::console::clients::checker::console::Console;
use crate::console::clients::checker::printer::Printer;
use crate::console::clients::checker::service::{CheckError, CheckResult};

pub async fn run(health_checks: &Vec<ServiceUrl>, console: &Console, check_results: &mut Vec<CheckResult>) {
console.println("Health checks ...");

for health_check_url in health_checks {
match run_health_check(health_check_url.clone(), console).await {
Ok(()) => check_results.push(Ok(())),
Err(err) => check_results.push(Err(err)),
}
}
#[derive(Debug, Clone, Error)]

Check warning on line 15 in src/console/clients/checker/checks/health.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/health.rs#L15

Added line #L15 was not covered by tests
pub enum Error {
#[error("Failed to Build a Http Client: {err:?}")]
ClientBuildingError { err: Arc<reqwest::Error> },

Check warning on line 18 in src/console/clients/checker/checks/health.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/health.rs#L18

Added line #L18 was not covered by tests
#[error("Heath check failed to get a response: {err:?}")]
ResponseError { err: Arc<reqwest::Error> },
#[error("Http check returned a non-success code: \"{code}\" with the response: \"{response:?}\"")]
UnsuccessfulResponse { code: StatusCode, response: Arc<Response> },

Check warning on line 22 in src/console/clients/checker/checks/health.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/health.rs#L20-L22

Added lines #L20 - L22 were not covered by tests
}

async fn run_health_check(url: Url, console: &Console) -> Result<(), CheckError> {
let client = HttpClient::builder().timeout(Duration::from_secs(5)).build().unwrap();
pub async fn run(health_checks: Vec<Url>, timeout: Duration, console: Console) -> Vec<CheckResult> {

Check warning on line 25 in src/console/clients/checker/checks/health.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/health.rs#L25

Added line #L25 was not covered by tests
let mut check_results = Vec::default();

let colored_url = url.to_string().yellow();
console.println("Health checks ...");

for url in health_checks {
let colored_url = url.to_string().yellow();

match client.get(url.clone()).send().await {
Ok(response) => {
if response.status().is_success() {
console.println(&format!("{} - Health API at {} is OK", "✓".green(), colored_url));
Ok(())
} else {
console.eprintln(&format!(
"{} - Health API at {} is failing: {:?}",
"✗".red(),
match run_health_check(url.clone(), timeout).await {
Ok(response) => {
console.println(&format!(
"{} - Health API at {} is {}",
"✓".green(),
colored_url,
response
response.status()
));
Err(CheckError::HealthCheckError { url })

check_results.push(Ok(()));
}
Err(err) => {
console.eprintln(&format!("{} - Health API at {} is failing: {}", "✗".red(), colored_url, err));

check_results.push(Err(CheckError::HealthCheckError { url, err }));
}
}
Err(err) => {
console.eprintln(&format!(
"{} - Health API at {} is failing: {:?}",
"✗".red(),
colored_url,
err
));
Err(CheckError::HealthCheckError { url })
}
}

check_results
}

Check warning on line 53 in src/console/clients/checker/checks/health.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/health.rs#L53

Added line #L53 was not covered by tests

async fn run_health_check(url: Url, timeout: Duration) -> Result<Response, Error> {

Check warning on line 55 in src/console/clients/checker/checks/health.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/health.rs#L55

Added line #L55 was not covered by tests
let client = HttpClient::builder()
.timeout(timeout)
.build()
.map_err(|e| Error::ClientBuildingError { err: e.into() })?;

let response = client
.get(url.clone())
.send()
.await
.map_err(|e| Error::ResponseError { err: e.into() })?;

if response.status().is_success() {
Ok(response)
} else {
Err(Error::UnsuccessfulResponse {
code: response.status(),
response: response.into(),
})
}
}
128 changes: 70 additions & 58 deletions src/console/clients/checker/checks/http.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;

use anyhow::Result;
use colored::Colorize;
use log::debug;
use reqwest::Url as ServiceUrl;
use thiserror::Error;
use torrust_tracker_primitives::info_hash::InfoHash;
use url::Url;

Expand All @@ -14,82 +16,92 @@ use crate::shared::bit_torrent::tracker::http::client::responses::announce::Anno
use crate::shared::bit_torrent::tracker::http::client::responses::scrape;
use crate::shared::bit_torrent::tracker::http::client::{requests, Client};

pub async fn run(http_trackers: &Vec<ServiceUrl>, console: &Console, check_results: &mut Vec<CheckResult>) {
#[derive(Debug, Clone, Error)]

Check warning on line 19 in src/console/clients/checker/checks/http.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/http.rs#L19

Added line #L19 was not covered by tests
pub enum Error {
#[error("Http request did not receive a response within the timeout: {err:?}")]
HttpClientError {
err: crate::shared::bit_torrent::tracker::http::client::Error,

Check warning on line 23 in src/console/clients/checker/checks/http.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/http.rs#L23

Added line #L23 was not covered by tests
},
#[error("Http failed to get a response at all: {err:?}")]
ResponseError { err: Arc<reqwest::Error> },

Check warning on line 26 in src/console/clients/checker/checks/http.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/http.rs#L26

Added line #L26 was not covered by tests
#[error("Failed to deserialize the serde bencoded response data with the error: \"{err:?}\"")]
ParseSerdeBencodeError {
data: hyper::body::Bytes,
err: Arc<serde_bencode::Error>,

Check warning on line 30 in src/console/clients/checker/checks/http.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/http.rs#L29-L30

Added lines #L29 - L30 were not covered by tests
},

#[error("Failed to deserialize the bencoded response data with the error: \"{err:?}\"")]
ParseScrapeBencodeError {
data: hyper::body::Bytes,
err: Arc<scrape::BencodeParseError>,

Check warning on line 36 in src/console/clients/checker/checks/http.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/http.rs#L35-L36

Added lines #L35 - L36 were not covered by tests
},
}

pub async fn run(http_trackers: Vec<Url>, timeout: Duration, console: Console) -> Vec<CheckResult> {

Check warning on line 40 in src/console/clients/checker/checks/http.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/http.rs#L40

Added line #L40 was not covered by tests
let mut check_results = Vec::default();

console.println("HTTP trackers ...");

for http_tracker in http_trackers {
let colored_tracker_url = http_tracker.to_string().yellow();
for ref url in http_trackers {
let colored_url = url.to_string().yellow();

match check_http_announce(http_tracker).await {
Ok(()) => {
check_results.push(Ok(()));
console.println(&format!("{} - Announce at {} is OK", "✓".green(), colored_tracker_url));
check_results.push(match check_http_announce(url, timeout).await {
Ok(_) => {
console.println(&format!("{} - Announce at {} is OK", "✓".green(), colored_url));
Ok(())
}
Err(err) => {
check_results.push(Err(err));
console.println(&format!("{} - Announce at {} is failing", "✗".red(), colored_tracker_url));
console.println(&format!("{} - Announce at {} is failing", "✗".red(), colored_url));
Err(CheckError::HttpCheckError { url: url.clone(), err })
}
}
});

match check_http_scrape(http_tracker).await {
Ok(()) => {
check_results.push(Ok(()));
console.println(&format!("{} - Scrape at {} is OK", "✓".green(), colored_tracker_url));
check_results.push(match check_http_scrape(url, timeout).await {
Ok(_) => {
console.println(&format!("{} - Scrape at {} is OK", "✓".green(), colored_url));
Ok(())
}
Err(err) => {
check_results.push(Err(err));
console.println(&format!("{} - Scrape at {} is failing", "✗".red(), colored_tracker_url));
console.println(&format!("{} - Scrape at {} is failing", "✗".red(), colored_url));
Err(CheckError::HttpCheckError { url: url.clone(), err })
}
}
});
}

check_results
}

async fn check_http_announce(tracker_url: &Url) -> Result<(), CheckError> {
async fn check_http_announce(url: &Url, timeout: Duration) -> Result<Announce, Error> {

Check warning on line 74 in src/console/clients/checker/checks/http.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/http.rs#L74

Added line #L74 was not covered by tests
let info_hash_str = "9c38422213e30bff212b30c360d26f9a02136422".to_string(); // # DevSkim: ignore DS173237
let info_hash = InfoHash::from_str(&info_hash_str).expect("a valid info-hash is required");

// todo: HTTP request could panic.For example, if the server is not accessible.
// We should change the client to catch that error and return a `CheckError`.
// Otherwise the checking process will stop. The idea is to process all checks
// and return a final report.
let response = Client::new(tracker_url.clone())
let client = Client::new(url.clone(), timeout).map_err(|err| Error::HttpClientError { err })?;

let response = client
.announce(&QueryBuilder::with_default_values().with_info_hash(&info_hash).query())
.await;

if let Ok(body) = response.bytes().await {
if let Ok(_announce_response) = serde_bencode::from_bytes::<Announce>(&body) {
Ok(())
} else {
debug!("announce body {:#?}", body);
Err(CheckError::HttpError {
url: tracker_url.clone(),
})
}
} else {
Err(CheckError::HttpError {
url: tracker_url.clone(),
})
}
.await
.map_err(|err| Error::HttpClientError { err })?;

let body = response.bytes().await.map_err(|e| Error::ResponseError { err: e.into() })?;

serde_bencode::from_bytes::<Announce>(&body).map_err(|e| Error::ParseSerdeBencodeError {
data: body,
err: e.into(),
})
}

async fn check_http_scrape(url: &Url) -> Result<(), CheckError> {
async fn check_http_scrape(url: &Url, timeout: Duration) -> Result<scrape::Response, Error> {

Check warning on line 93 in src/console/clients/checker/checks/http.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/http.rs#L93

Added line #L93 was not covered by tests
let info_hashes: Vec<String> = vec!["9c38422213e30bff212b30c360d26f9a02136422".to_string()]; // # DevSkim: ignore DS173237
let query = requests::scrape::Query::try_from(info_hashes).expect("a valid array of info-hashes is required");

// todo: HTTP request could panic.For example, if the server is not accessible.
// We should change the client to catch that error and return a `CheckError`.
// Otherwise the checking process will stop. The idea is to process all checks
// and return a final report.
let response = Client::new(url.clone()).scrape(&query).await;

if let Ok(body) = response.bytes().await {
if let Ok(_scrape_response) = scrape::Response::try_from_bencoded(&body) {
Ok(())
} else {
debug!("scrape body {:#?}", body);
Err(CheckError::HttpError { url: url.clone() })
}
} else {
Err(CheckError::HttpError { url: url.clone() })
}
let client = Client::new(url.clone(), timeout).map_err(|err| Error::HttpClientError { err })?;

let response = client.scrape(&query).await.map_err(|err| Error::HttpClientError { err })?;

let body = response.bytes().await.map_err(|e| Error::ResponseError { err: e.into() })?;

scrape::Response::try_from_bencoded(&body).map_err(|e| Error::ParseScrapeBencodeError {
data: body,
err: e.into(),
})
}
9 changes: 7 additions & 2 deletions src/console/clients/checker/checks/udp.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::net::SocketAddr;
use std::time::Duration;

use aquatic_udp_protocol::{Port, TransactionId};
use colored::Colorize;
Expand All @@ -14,10 +15,12 @@ use crate::console::clients::udp::checker;
const ASSIGNED_BY_OS: u16 = 0;
const RANDOM_TRANSACTION_ID: i32 = -888_840_697;

pub async fn run(udp_trackers: &Vec<SocketAddr>, console: &Console, check_results: &mut Vec<CheckResult>) {
pub async fn run(udp_trackers: Vec<SocketAddr>, _timeout: Duration, console: Console) -> Vec<CheckResult> {

Check warning on line 18 in src/console/clients/checker/checks/udp.rs

View check run for this annotation

Codecov / codecov/patch

src/console/clients/checker/checks/udp.rs#L18

Added line #L18 was not covered by tests
let mut check_results = Vec::default();

console.println("UDP trackers ...");

for udp_tracker in udp_trackers {
for ref udp_tracker in udp_trackers {
debug!("UDP tracker: {:?}", udp_tracker);

let colored_tracker_url = udp_tracker.to_string().yellow();
Expand Down Expand Up @@ -84,4 +87,6 @@ pub async fn run(udp_trackers: &Vec<SocketAddr>, console: &Console, check_result
console.println(&format!("{} - Announce at {} is failing", "✗".red(), colored_tracker_url));
}
}

check_results
}
Loading

0 comments on commit 2933092

Please sign in to comment.