#[allow(dead_code)]
use serde_derive::{Serialize, Deserialize};
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::time::Duration;
use regex::Regex;
use async_std::sync::{Arc, RwLock};
use surf::http::Method;
use surf::{Error, StatusCode};
use async_std::task::block_on;
use super::catalog;
use super::api;
use super::agent;
lazy_static!(
#[derive(Debug)]
pub static ref HEALTH_ANY: Arc<String> = {
Arc::new(String::from("any"))
};
#[derive(Debug)]
pub static ref HEALTH_PASSING:Arc<String> = {
Arc::new(String::from("passing"))
};
#[derive(Debug)]
pub static ref HEALTH_WARNING:Arc<String> = {
Arc::new(String::from("warning"))
};
#[derive(Debug)]
pub static ref HEALTH_CRITICAL:Arc<String> = {
Arc::new(String::from("critical"))
};
#[derive(Debug)]
pub static ref HEALTH_MAINT:Arc<String> = {
Arc::new(String::from("maintenance"))
};
);
lazy_static!(
#[derive(Debug)]
static ref SERVICE_HEALTH:String = {
String::from("service")
};
#[derive(Debug)]
static ref CONNECT_HEALTH:String = {
String::from("connect")
};
#[derive(Debug)]
static ref INGRESS_HEALTH:String = {
String::from("ingress")
};
);
lazy_static!(
#[derive(Debug)]
pub static ref NODE_MAINT:Arc<String> = {
Arc::new(String::from("_node_maintenance"))
};
#[derive(Debug)]
pub static ref SERVICE_MAINT_PREFIX:Arc<String> = {
Arc::new(String::from("_service_maintenance:"))
};
);
lazy_static! {
pub static ref HEALTH: Arc<RwLock<Health>> = {
let client = api::CLIENT.clone();
let lock = block_on(client.read());
let health = block_on(lock.health());
Arc::new(RwLock::new(health))
};
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[allow(non_snake_case)]
pub struct HealthCheck {
pub Node: Option<String>,
pub CheckID: Option<String>,
pub Name: Option<String>,
pub Status: Option<String>,
pub Notes: Option<String>,
pub Output: Option<String>,
pub ServiceID: Option<String>,
pub ServiceName: Option<String>,
pub ServiceTags: Option<Vec<String>>,
pub Type: Option<String>,
pub Namespace: Option<String>,
pub Definition: Option<HealthCheckDefinition>,
pub CreateIndex: Option<usize>,
pub ModifyIndex: Option<usize>,
}
type ReadableDuration = Duration;
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct HealthCheckDefinition {
pub HTTP: Option<String>,
pub Header: Option<HashMap<String, Vec<String>>>,
pub Method: Option<String>,
pub Body: Option<String>,
pub TLSServerName: Option<String>,
pub TLSSkipVerify: Option<bool>,
pub TCP: Option<String>,
pub IntervalDuration: Option<Duration>,
pub TimeoutDuration: Option<Duration>,
pub DeregisterCriticalServiceAfterDuration: Option<Duration>,
pub Interval: Option<ReadableDuration>,
pub Timeout: Option<ReadableDuration>,
pub DeregisterCriticalServiceAfter: Option<ReadableDuration>,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct HealthChecks(Vec<HealthCheck>);
impl HealthChecks {
pub async fn aggregates_status(&self) -> String {
let mut passing: bool = false;
let mut warning: bool = false;
let mut critical: bool = false;
let mut maintenance: bool = false;
for check in self.0.iter() {
if check.CheckID.is_some() {
let id = check.CheckID.as_ref().unwrap();
let pat = format!("^{:?}", &*SERVICE_MAINT_PREFIX.clone());
let re = Regex::new(&pat).unwrap();
let node_main = &*NODE_MAINT.clone();
if id == node_main || re.is_match(&id) {
maintenance = true;
continue;
}
}
if check.Status.is_some() {
let status = check.Status.as_ref().unwrap();
let p = &*HEALTH_PASSING.clone();
let w = &*HEALTH_WARNING.clone();
if status == p {
passing = true
} else if status == w {
warning = true
} else if status == &*HEALTH_CRITICAL.clone() {
critical = true
} else {
return String::new();
}
} else {
return String::new();
}
}
return if maintenance {
let s = &*HEALTH_MAINT.clone();
s.into()
} else if critical {
let s = &*HEALTH_CRITICAL.clone();
s.into()
} else if warning {
let s = &*HEALTH_WARNING.clone();
s.into()
} else if passing {
let s = &*HEALTH_PASSING.clone();
s.into()
} else {
let s = &*HEALTH_PASSING.clone();
s.into()
};
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct ServiceEntry {
pub Node: Option<catalog::Node>,
pub Service: Option<agent::AgentService>,
pub Checks: Option<HealthChecks>,
}
#[derive(Default, Debug)]
pub struct Health {
pub c: Option<api::Client>,
}
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct Tag {
pub tag: String,
}
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct Passing {
pub passing: String,
}
impl Health {
pub async fn reload_client() {
let client = api::CLIENT.clone();
let client = client.read().await;
let s = client.health().await;
let health = HEALTH.clone();
let mut health = health.write().await;
*health = s;
}
pub async fn service(&self, service: &str, tag: &str, passing_only: bool, q: Option<api::QueryOptions>)
-> surf::Result<Vec<ServiceEntry>> {
let mut tags = vec![];
if tag != "" {
tags.push(tag);
}
self.service_private(service, tags, passing_only, q, &CONNECT_HEALTH).await
}
async fn service_private(&self, service: &str, tags: Vec<&str>, passing_only: bool, q: Option<api::QueryOptions>, health_type: &str)
-> surf::Result<Vec<ServiceEntry>> {
let path;
match health_type {
"service" => {
path = format!("/v1/health/connect/{}", service)
}
"ingress" => {
path = format!("/v1/health/ingress/{}", service)
}
_ => {
path = format!("/v1/health/service/{}", service)
}
}
if self.c.is_some() {
let client = self.c.unwrap();
let mut req = client.new_request(Method::Get, path).await?;
if q.is_some() {
let opts = q.unwrap();
req.set_query(&opts)?;
}
if tags.len() > 0 {
for tag in tags.iter() {
let cur_tag = Tag { tag: tag.to_string() };
req.set_query(&cur_tag)?;
}
}
if passing_only {
let query = Passing { passing: String::from("1") };
req.set_query(&query)?;
};
let client = surf::Client::new();
let mut res = client.send(req).await?;
let out: Vec<ServiceEntry> = res.body_json().await?;
Ok(out)
} else {
Err(Error::from_str(StatusCode::BadRequest, "client init err"))
}
}
pub async fn service_address(&self, service: &str, tag: &str, passing_only: bool, q: Option<api::QueryOptions>)
-> surf::Result<Vec<String>> {
let entry = self.service(service, tag, passing_only, q).await?;
let mut service_addresses = vec![];
for val in entry.iter() {
if val.Service.is_some() {
let v = val.Service.as_ref().unwrap();
if v.Address.is_some() && v.Port.is_some() {
let address = v.Address.as_ref().unwrap();
let port = v.Port.as_ref().unwrap();
let address = format!("{}:{}", address, port);
service_addresses.push(address);
};
};
};
Ok(service_addresses)
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct QueryMeta {
pub LastIndex: Option<u64>,
pub LastContentHash: Option<String>,
pub LastContact: Option<Duration>,
pub KnownLeader: Option<bool>,
pub RequestTime: Option<Duration>,
pub AddressTranslationEnabled: Option<bool>,
pub CacheHit: Option<bool>,
pub CacheAge: Option<Duration>,
pub DefaultACLPolicy: Option<String>,
}
#[cfg(test)]
mod tests {
use async_std::task::block_on;
use super::Health;
use crate::api;
#[test]
fn test_service() {
let client = api::CLIENT.clone();
let c = block_on(client.read());
let health = block_on(c.health());
let s = block_on(health.service("test", "", true, None)).unwrap();
println!("{:?}", s)
}
#[test]
fn test_service_address() {
block_on(api::Client::set_config_address("http://0.0.0.0:8500"));
block_on(Health::reload_client());
let client = api::CLIENT.clone();
let c = block_on(client.read());
let health = block_on(c.health());
let s = block_on(health.service_address("test", "", true, None)).unwrap();
println!("{:?}", s)
}
}