|
|
@ -1,12 +1,14 @@
|
|
|
|
use crate::modules::heartbeat::payloads::HeartbeatPayload;
|
|
|
|
use crate::modules::heartbeat::payloads::HeartbeatPayload;
|
|
|
|
use crate::modules::heartbeat::settings::HeartbeatSettings;
|
|
|
|
use crate::modules::heartbeat::settings::HeartbeatSettings;
|
|
|
|
use crate::modules::Module;
|
|
|
|
use crate::modules::Module;
|
|
|
|
use crate::server::tick_context::TickContext;
|
|
|
|
use crate::server::tick_context::RunContext;
|
|
|
|
use crate::utils::result::SnekcloudResult;
|
|
|
|
use crate::utils::result::SnekcloudResult;
|
|
|
|
use crate::utils::settings::get_settings;
|
|
|
|
use crate::utils::settings::get_settings;
|
|
|
|
use crate::utils::write_json_pretty;
|
|
|
|
use crate::utils::write_json_pretty;
|
|
|
|
|
|
|
|
use async_std::task;
|
|
|
|
|
|
|
|
use async_trait::async_trait;
|
|
|
|
|
|
|
|
use chrono::Local;
|
|
|
|
use parking_lot::Mutex;
|
|
|
|
use parking_lot::Mutex;
|
|
|
|
use scheduled_thread_pool::ScheduledThreadPool;
|
|
|
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::sync::Arc;
|
|
|
|
use std::sync::Arc;
|
|
|
@ -28,6 +30,7 @@ enum NodeState {
|
|
|
|
struct NodeInfo {
|
|
|
|
struct NodeInfo {
|
|
|
|
ping: Option<u64>,
|
|
|
|
ping: Option<u64>,
|
|
|
|
state: NodeState,
|
|
|
|
state: NodeState,
|
|
|
|
|
|
|
|
timestamp: String,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl NodeInfo {
|
|
|
|
impl NodeInfo {
|
|
|
@ -35,18 +38,19 @@ impl NodeInfo {
|
|
|
|
Self {
|
|
|
|
Self {
|
|
|
|
ping: Some(ping),
|
|
|
|
ping: Some(ping),
|
|
|
|
state: NodeState::Alive,
|
|
|
|
state: NodeState::Alive,
|
|
|
|
|
|
|
|
timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S").to_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn dead() -> Self {
|
|
|
|
fn dead() -> Self {
|
|
|
|
Self {
|
|
|
|
Self {
|
|
|
|
ping: None,
|
|
|
|
ping: None,
|
|
|
|
state: NodeState::Dead,
|
|
|
|
state: NodeState::Dead,
|
|
|
|
|
|
|
|
timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S").to_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub struct HeartbeatModule {
|
|
|
|
pub struct HeartbeatModule {
|
|
|
|
last_tick: Instant,
|
|
|
|
|
|
|
|
settings: HeartbeatSettings,
|
|
|
|
settings: HeartbeatSettings,
|
|
|
|
node_states: Arc<Mutex<HashMap<String, Vec<NodeInfo>>>>,
|
|
|
|
node_states: Arc<Mutex<HashMap<String, Vec<NodeInfo>>>>,
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -54,23 +58,19 @@ pub struct HeartbeatModule {
|
|
|
|
impl HeartbeatModule {
|
|
|
|
impl HeartbeatModule {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
Self {
|
|
|
|
last_tick: Instant::now(),
|
|
|
|
|
|
|
|
settings: get_settings().modules.heartbeat,
|
|
|
|
settings: get_settings().modules.heartbeat,
|
|
|
|
node_states: Arc::new(Mutex::new(HashMap::new())),
|
|
|
|
node_states: Arc::new(Mutex::new(HashMap::new())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
impl Module for HeartbeatModule {
|
|
|
|
impl Module for HeartbeatModule {
|
|
|
|
fn name(&self) -> String {
|
|
|
|
fn name(&self) -> String {
|
|
|
|
"HeartbeatModule".to_string()
|
|
|
|
"HeartbeatModule".to_string()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn init(
|
|
|
|
fn init(&mut self, server: &mut VentedServer) -> SnekcloudResult<()> {
|
|
|
|
&mut self,
|
|
|
|
|
|
|
|
server: &mut VentedServer,
|
|
|
|
|
|
|
|
pool: &mut ScheduledThreadPool,
|
|
|
|
|
|
|
|
) -> SnekcloudResult<()> {
|
|
|
|
|
|
|
|
server.on(HEARTBEAT_BEAT_EVENT, {
|
|
|
|
server.on(HEARTBEAT_BEAT_EVENT, {
|
|
|
|
let node_states = Arc::clone(&self.node_states);
|
|
|
|
let node_states = Arc::clone(&self.node_states);
|
|
|
|
|
|
|
|
|
|
|
@ -92,19 +92,6 @@ impl Module for HeartbeatModule {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
if let Some(output) = &self.settings.output_file {
|
|
|
|
|
|
|
|
pool.execute_at_fixed_rate(self.settings.interval(), self.settings.interval(), {
|
|
|
|
|
|
|
|
let path = output.clone();
|
|
|
|
|
|
|
|
let states = Arc::clone(&self.node_states);
|
|
|
|
|
|
|
|
move || {
|
|
|
|
|
|
|
|
let states = states.lock();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if let Err(e) = write_json_pretty(&path, &*states) {
|
|
|
|
|
|
|
|
log::error!("Failed to write output states to file: {}", e)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -113,40 +100,39 @@ impl Module for HeartbeatModule {
|
|
|
|
Box::new(self)
|
|
|
|
Box::new(self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn tick(
|
|
|
|
async fn run(&mut self, context: RunContext) -> SnekcloudResult<()> {
|
|
|
|
&mut self,
|
|
|
|
for node in context.nodes() {
|
|
|
|
mut context: TickContext,
|
|
|
|
let mut context = context.clone();
|
|
|
|
pool: &mut ScheduledThreadPool,
|
|
|
|
let node_states = Arc::clone(&self.node_states);
|
|
|
|
) -> SnekcloudResult<()> {
|
|
|
|
let interval = self.settings.interval();
|
|
|
|
if self.last_tick.elapsed() > self.settings.interval() {
|
|
|
|
|
|
|
|
log::trace!("Sending heartbeat...");
|
|
|
|
task::spawn(async move {
|
|
|
|
for node in context.living_nodes() {
|
|
|
|
loop {
|
|
|
|
let mut future = context.emit(
|
|
|
|
Self::send_heartbeat(&mut context, &node.id, Arc::clone(&node_states)).await;
|
|
|
|
node.id.clone(),
|
|
|
|
|
|
|
|
Event::with_payload(
|
|
|
|
if !context.check_alive(&node.id) {
|
|
|
|
HEARTBEAT_BEAT_EVENT,
|
|
|
|
let start = Instant::now();
|
|
|
|
&HeartbeatPayload::now(context.node_id().clone()),
|
|
|
|
while !context.check_alive(&node.id) {
|
|
|
|
),
|
|
|
|
task::sleep(Duration::from_secs(10)).await;
|
|
|
|
);
|
|
|
|
if start.elapsed() > interval * 100 {
|
|
|
|
let states = Arc::clone(&self.node_states);
|
|
|
|
break;
|
|
|
|
pool.execute(move || {
|
|
|
|
|
|
|
|
match future.get_value_with_timeout(Duration::from_secs(60)) {
|
|
|
|
|
|
|
|
Some(Err(e)) => {
|
|
|
|
|
|
|
|
log::debug!("Node {} is not reachable: {}", node.id, e);
|
|
|
|
|
|
|
|
Self::insert_state(&mut states.lock(), node.id, NodeInfo::dead());
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
|
|
|
|
log::debug!("Node {} is not reachable: Timeout", node.id);
|
|
|
|
|
|
|
|
Self::insert_state(&mut states.lock(), node.id, NodeInfo::dead());
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
} else {
|
|
|
|
|
|
|
|
task::sleep(interval).await
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.last_tick = Instant::now();
|
|
|
|
loop {
|
|
|
|
|
|
|
|
if let Some(path) = &self.settings.output_file {
|
|
|
|
|
|
|
|
let states = self.node_states.lock();
|
|
|
|
|
|
|
|
if let Err(e) = write_json_pretty(path, &*states) {
|
|
|
|
|
|
|
|
log::error!("Failed to write output states to file: {}", e)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
task::sleep(self.settings.interval()).await
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -164,4 +150,36 @@ impl HeartbeatModule {
|
|
|
|
states.insert(id, vec![state]);
|
|
|
|
states.insert(id, vec![state]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async fn send_heartbeat(
|
|
|
|
|
|
|
|
context: &mut RunContext,
|
|
|
|
|
|
|
|
target: &String,
|
|
|
|
|
|
|
|
states: Arc<Mutex<HashMap<String, Vec<NodeInfo>>>>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
log::trace!("Sending heartbeat to {}...", target);
|
|
|
|
|
|
|
|
let mut value = context
|
|
|
|
|
|
|
|
.emit(
|
|
|
|
|
|
|
|
target.clone(),
|
|
|
|
|
|
|
|
Event::with_payload(
|
|
|
|
|
|
|
|
HEARTBEAT_BEAT_EVENT,
|
|
|
|
|
|
|
|
&HeartbeatPayload::now(context.node_id().clone()),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
match value
|
|
|
|
|
|
|
|
.get_value_with_timeout_async(Duration::from_secs(60))
|
|
|
|
|
|
|
|
.await
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Some(Err(e)) => {
|
|
|
|
|
|
|
|
log::debug!("Node {} is not reachable: {}", target, e);
|
|
|
|
|
|
|
|
Self::insert_state(&mut *states.lock(), target.clone(), NodeInfo::dead());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
None => {
|
|
|
|
|
|
|
|
log::debug!("Node {} is not reachable: Timeout", target);
|
|
|
|
|
|
|
|
Self::insert_state(&mut *states.lock(), target.clone(), NodeInfo::dead());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => {}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|