Add more detailed Http mode config

This commit is contained in:
2021-02-01 16:17:03 +01:00
parent 9984c422ae
commit ef049e5171
2 changed files with 78 additions and 11 deletions

View File

@ -3,7 +3,10 @@
services: { services: {
"example": ( "example": (
url: "https://example.org", url: "https://example.org",
mode: Check200, mode: (
up_status_codes: [Status2XX],
down_status_codes: [Status(404)],
),
), ),
}, },
) )

View File

@ -1,20 +1,21 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use futures::lock::Mutex; use futures::lock::Mutex;
use log::info; use log::{error, info};
use reqwest::{Response, StatusCode};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
pub type ServiceId = String; pub type ServiceId = String;
#[derive(Debug)] #[derive(Debug, Copy, Clone)]
pub enum HealthStatus { pub enum HealthStatus {
/// The service is up and running /// The service is up and running
Up, Up,
/// The service gave an error when performing the health check /// The service gave a valid error when performing the health check
Errored, Errored,
/// The service did not respond to the health check /// The service seems to be completely offline
Down, Down,
} }
@ -25,9 +26,26 @@ pub struct ServiceConfig {
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub enum HttpHealthCheckMode { pub enum PartialStatusCode {
/// Expect the server to return a 200 status code. The body is ignored. Any,
Check200, Status1XX,
Status2XX,
Status3XX,
Status4XX,
Status5XX,
Status(u16),
}
/// If the response status code matches matches any in `ok_status_codes`, status will be Up
/// else if it matches any in `down_status_codes` status will be Down, else status will be Errored
#[derive(Serialize, Deserialize)]
pub struct HttpHealthCheckMode {
/// If the response status code matches one in this list, the status will be Up
pub up_status_codes: Vec<PartialStatusCode>,
/// If the response status code matches one in this list, the status will be Down
#[serde(default)]
pub down_status_codes: Vec<PartialStatusCode>,
} }
pub struct HealthState { pub struct HealthState {
@ -62,15 +80,61 @@ impl HealthState {
*self.last_update.lock().await = Some(Utc::now()); *self.last_update.lock().await = Some(Utc::now());
for (id, status) in &self.health { for (id, status) in &self.health {
let url = &self.config.services[id].url; let service_config = &self.config.services[id];
let url = &service_config.url;
info!("service [{}] querying {}", id, url); info!("service [{}] querying {}", id, url);
let new_status = match reqwest::get(url).await { let new_status = match reqwest::get(url).await {
Err(_) => HealthStatus::Down, Err(_) => HealthStatus::Down,
Ok(response) if response.status().is_success() => HealthStatus::Up, Ok(response) => service_config.mode.status_of(&response),
Ok(_response) => HealthStatus::Errored,
}; };
info!("service [{}] new status {:?}", id, new_status); info!("service [{}] new status {:?}", id, new_status);
*status.lock().await = Some(new_status); *status.lock().await = Some(new_status);
} }
} }
} }
impl HttpHealthCheckMode {
fn status_of(&self, response: &Response) -> HealthStatus {
let response_status = response.status();
fn validate_status(
expected: &PartialStatusCode,
actual: StatusCode,
if_valid: HealthStatus,
) -> Option<HealthStatus> {
let if_valid = Some(if_valid);
match expected {
PartialStatusCode::Any => if_valid,
&PartialStatusCode::Status(code) => StatusCode::from_u16(code)
.map_err(|e| error!("invalid status code: {}", e))
.ok()
.filter(|status| status == &actual)
.and_then(|_| if_valid),
PartialStatusCode::Status5XX if actual.is_server_error() => if_valid,
PartialStatusCode::Status4XX if actual.is_server_error() => if_valid,
PartialStatusCode::Status3XX if actual.is_redirection() => if_valid,
PartialStatusCode::Status2XX if actual.is_success() => if_valid,
PartialStatusCode::Status1XX if actual.is_informational() => if_valid,
_ => None,
}
}
// Check if response status matches expected status codes for Up
let check_up = self
.up_status_codes
.iter()
.flat_map(|up_code| validate_status(&up_code, response_status, HealthStatus::Up));
// Check if response status matches expected status codes for Down
let check_down = self
.down_status_codes
.iter()
.flat_map(|down_code| validate_status(&down_code, response_status, HealthStatus::Down));
// Compute status, defaulting to Errored if neigher Up nor Down matched
check_up
.chain(check_down)
.next()
.unwrap_or(HealthStatus::Errored)
}
}