forked from torrust/torrust-index
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ci: [torrust#384] add container healthcheck for index service
For both services: - The Index API - The Tracker Stattistics Importer (console cronjob)
- Loading branch information
1 parent
04b70ba
commit 05d7ac9
Showing
9 changed files
with
197 additions
and
34 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
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
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
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
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
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,37 @@ | ||
//! Minimal `curl` or `wget` to be used for container health checks. | ||
//! | ||
//! IT's convenient to avoid using third-party libraries because: | ||
//! | ||
//! - They are harder to maintain. | ||
//! - They introduce new attack vectors. | ||
use std::{env, process}; | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let args: Vec<String> = env::args().collect(); | ||
if args.len() != 2 { | ||
eprintln!("Usage: cargo run --bin health_check <HEALTH_URL>"); | ||
eprintln!("Example: cargo run --bin health_check http://localhost:3002/health_check"); | ||
std::process::exit(1); | ||
} | ||
|
||
println!("Health check ..."); | ||
|
||
let url = &args[1].clone(); | ||
|
||
match reqwest::get(url).await { | ||
Ok(response) => { | ||
if response.status().is_success() { | ||
println!("STATUS: {}", response.status()); | ||
process::exit(0); | ||
} else { | ||
println!("Non-success status received."); | ||
process::exit(1); | ||
} | ||
} | ||
Err(err) => { | ||
println!("ERROR: {err}"); | ||
process::exit(1); | ||
} | ||
} | ||
} |
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 |
---|---|---|
@@ -1 +1,2 @@ | ||
pub mod commands; | ||
pub(crate) mod tracker_statistics_importer; |
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,127 @@ | ||
//! Cronjob to import tracker torrent data and updating seeders and leechers | ||
//! info. | ||
//! | ||
//! It has two services: | ||
//! | ||
//! - The importer which is the cronjob executed at regular intervals. | ||
//! - The importer API. | ||
//! | ||
//! The cronjob sends a heartbeat signal to the API each time it is executed. | ||
//! The last heartbeat signal time is used to determine whether the cronjob was | ||
//! executed successfully or not. The API has a `health_check` endpoint which is | ||
//! used when the application is running in containers. | ||
use std::sync::{Arc, Mutex}; | ||
|
||
use axum::extract::State; | ||
use axum::routing::{get, post}; | ||
use axum::{Json, Router}; | ||
use chrono::{DateTime, Utc}; | ||
use log::{error, info}; | ||
use serde_json::{json, Value}; | ||
use tokio::task::JoinHandle; | ||
|
||
use crate::tracker::statistics_importer::StatisticsImporter; | ||
|
||
const IMPORTER_API_IP: &str = "127.0.0.1"; | ||
const IMPORTER_API_PORT: u16 = 3002; // todo: use configuration option | ||
|
||
#[derive(Clone)] | ||
struct ImporterState { | ||
/// Shared variable to store the timestamp of the last heartbeat sent | ||
/// by the cronjob. | ||
pub last_heartbeat: Arc<Mutex<DateTime<Utc>>>, | ||
/// Interval between importation executions | ||
pub torrent_info_update_interval: u64, | ||
} | ||
|
||
pub fn start(torrent_info_update_interval: u64, tracker_statistics_importer: &Arc<StatisticsImporter>) -> JoinHandle<()> { | ||
let weak_tracker_statistics_importer = Arc::downgrade(tracker_statistics_importer); | ||
|
||
tokio::spawn(async move { | ||
info!("Tracker statistics importer launcher started"); | ||
|
||
// Start the Importer API | ||
|
||
let _importer_api_handle = tokio::spawn(async move { | ||
let import_state = Arc::new(ImporterState { | ||
last_heartbeat: Arc::new(Mutex::new(Utc::now())), | ||
torrent_info_update_interval, | ||
}); | ||
|
||
let app = Router::new() | ||
.route("/", get(|| async { Json(json!({})) })) | ||
.route("/health_check", get(health_check_handler)) | ||
.with_state(import_state.clone()) | ||
.route("/heartbeat", post(heartbeat_handler)) | ||
.with_state(import_state); | ||
|
||
let addr = format!("{IMPORTER_API_IP}:{IMPORTER_API_PORT}"); | ||
|
||
info!("Tracker statistics importer API server listening on http://{}", addr); | ||
|
||
axum::Server::bind(&addr.parse().unwrap()) | ||
.serve(app.into_make_service()) | ||
.await | ||
.unwrap(); | ||
}); | ||
|
||
// Start the Importer cronjob | ||
|
||
info!("Tracker statistics importer cronjob starting ..."); | ||
|
||
let interval = std::time::Duration::from_secs(torrent_info_update_interval); | ||
let mut interval = tokio::time::interval(interval); | ||
|
||
interval.tick().await; // first tick is immediate... | ||
|
||
loop { | ||
interval.tick().await; | ||
|
||
info!("Running tracker statistics importer ..."); | ||
|
||
if let Err(e) = send_heartbeat().await { | ||
error!("Failed to send heartbeat from importer cronjob: {}", e); | ||
} | ||
|
||
if let Some(tracker) = weak_tracker_statistics_importer.upgrade() { | ||
drop(tracker.import_all_torrents_statistics().await); | ||
} else { | ||
break; | ||
} | ||
} | ||
}) | ||
} | ||
|
||
/// Endpoint for container health check. | ||
async fn health_check_handler(State(state): State<Arc<ImporterState>>) -> Json<Value> { | ||
let margin_in_seconds = 10; | ||
let now = Utc::now(); | ||
let last_heartbeat = state.last_heartbeat.lock().unwrap(); | ||
|
||
if now.signed_duration_since(*last_heartbeat).num_seconds() | ||
<= (state.torrent_info_update_interval + margin_in_seconds).try_into().unwrap() | ||
{ | ||
Json(json!({ "status": "Ok" })) | ||
} else { | ||
Json(json!({ "status": "Error" })) | ||
} | ||
} | ||
|
||
/// The tracker statistics importer cronjob sends a heartbeat on each execution | ||
/// to inform that it's alive. This endpoint handles receiving that signal. | ||
async fn heartbeat_handler(State(state): State<Arc<ImporterState>>) -> Json<Value> { | ||
let now = Utc::now(); | ||
let mut last_heartbeat = state.last_heartbeat.lock().unwrap(); | ||
*last_heartbeat = now; | ||
Json(json!({ "status": "Heartbeat received" })) | ||
} | ||
|
||
/// Send a heartbeat from the importer cronjob to the importer API. | ||
async fn send_heartbeat() -> Result<(), reqwest::Error> { | ||
let client = reqwest::Client::new(); | ||
let url = format!("http://{IMPORTER_API_IP}:{IMPORTER_API_PORT}/heartbeat"); | ||
|
||
client.post(url).send().await?; | ||
|
||
Ok(()) | ||
} |
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