refactor(notify): extract types to separate module and redesign
NotificationRule - Move NotifyEvent, NotifyPluginMap, and related types to notify/types.rs - Introduce NotificationRule struct replacing NotificationMethod fields - Add NotificationRulesMap for priority-based notification configuration - Comment out main event loop and add todo!() placeholders for WIP
This commit is contained in:
@@ -99,18 +99,6 @@ pub struct PluginRuntimeConfig {
|
||||
/// Notification method configuration
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct NotificationMethod {
|
||||
/// Whether notification failure should not trigger next priority group
|
||||
#[serde(default)]
|
||||
pub fallible: bool,
|
||||
|
||||
/// Number of retries on failure (0 = no retry, -1 = infinite retries)
|
||||
#[serde(default)]
|
||||
pub retry: i8,
|
||||
|
||||
/// Timeout for notification execution in milliseconds (0 = no timeout)
|
||||
#[serde(default)]
|
||||
pub timeout_ms: u64,
|
||||
|
||||
/// Template definition
|
||||
#[serde(default)]
|
||||
pub template: NotificationTemplate,
|
||||
|
||||
+55
-109
@@ -1,23 +1,24 @@
|
||||
use crate::constant::FINALIZE_STANDALONE_PLUGIN_PATH;
|
||||
use crate::finalize::FinalizeConfig;
|
||||
use crate::finalize::NotificationConfig;
|
||||
use crate::finalize::parse;
|
||||
use crate::finalize::plugin;
|
||||
use crate::finalize::plugin::FinalizePlugin;
|
||||
use crate::finalize::plugin::fetch;
|
||||
use crate::types::buildstatus::StagePhase;
|
||||
use crate::types::buildstatus::StageResult;
|
||||
use crate::notify::types::BundledNotifyEvent;
|
||||
use crate::notify::types::NOTIFY_TEMPORARY_DELAY_FACTOR;
|
||||
use crate::notify::types::NOTIFY_TEMPORARY_DELAY_TIME;
|
||||
use crate::notify::types::NOTIFY_TEMPORARY_MAX_TIME;
|
||||
use crate::notify::types::NotificationRulesMap;
|
||||
use crate::notify::types::NotifyEvent;
|
||||
use crate::notify::types::NotifyEventReceiver;
|
||||
use crate::notify::types::NotifyEventSender;
|
||||
use crate::notify::types::NotifyPluginMap;
|
||||
use crate::{EventSender, ExecutionContext};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod error;
|
||||
mod types;
|
||||
use error::NotifyError;
|
||||
|
||||
fn validate(finalize: &FinalizeConfig) -> anyhow::Result<()> {
|
||||
@@ -31,8 +32,6 @@ fn validate(finalize: &FinalizeConfig) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub type NotifyPluginMap = HashMap<String, FinalizePlugin>;
|
||||
|
||||
fn extract_plugin(finalize: &FinalizeConfig) -> HashSet<String> {
|
||||
finalize
|
||||
.notification
|
||||
@@ -43,60 +42,6 @@ fn extract_plugin(finalize: &FinalizeConfig) -> HashSet<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct NotifyEvent {
|
||||
id: Uuid,
|
||||
stage: StagePhase,
|
||||
substage: String,
|
||||
result: StageResult,
|
||||
not_before: DateTime<Utc>,
|
||||
priority: u32,
|
||||
effective_priority: u32,
|
||||
lives: u32,
|
||||
max_lives: u32,
|
||||
target: HashSet<String>, // Target audience
|
||||
}
|
||||
|
||||
impl Hash for NotifyEvent {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NotifyEvent {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for NotifyEvent {}
|
||||
|
||||
impl PartialOrd for NotifyEvent {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(
|
||||
self.effective_priority
|
||||
.cmp(&other.effective_priority)
|
||||
.then(self.not_before.cmp(&other.not_before))
|
||||
.reverse(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for NotifyEvent {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.partial_cmp(other).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
type BundledNotifyEvent = Vec<NotifyEvent>;
|
||||
type NotifyEventReceiver = tokio::sync::mpsc::Receiver<BundledNotifyEvent>;
|
||||
pub type NotifyEventSender = tokio::sync::mpsc::Sender<NotifyEvent>;
|
||||
|
||||
const NOTIFY_NOTREADY_DELAY: std::time::Duration = std::time::Duration::from_secs(5);
|
||||
const NOTIFY_TEMPORARY_DELAY_FACTOR: f64 = 2.0;
|
||||
const NOTIFY_TEMPORARY_DELAY_TIME: f64 = 5.0;
|
||||
const NOTIFY_TEMPORARY_MAX_TIME: f64 = f64::MAX;
|
||||
|
||||
pub async fn notify(
|
||||
finalize_path: &Path,
|
||||
ctx: &mut ExecutionContext,
|
||||
@@ -124,53 +69,54 @@ pub async fn notify(
|
||||
}
|
||||
let notify_plugins = plugin::register(finalize.plugin, Some(&plugin_list))?;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(events) = notifyevent_rx.recv() => {
|
||||
match notify_handler(¬ify_plugins, &finalize.notification, &events){
|
||||
Ok(_) => {}, // Notification succeeded / failed with fallible
|
||||
Err(e) => {
|
||||
log::debug!("Failed to send notification: {:?}", e);
|
||||
match e {
|
||||
NotifyError::PluginNotAvailable(_) => {
|
||||
// Should keep lives, add static delay and give less priority
|
||||
for mut event in events {
|
||||
event.not_before = Utc::now() + NOTIFY_NOTREADY_DELAY;
|
||||
event.effective_priority = event.effective_priority.saturating_add(1);
|
||||
notifyevent_tx.send(event).await?;
|
||||
}
|
||||
},
|
||||
NotifyError::TemporaryError(_) => {
|
||||
// Should cost 1 life, add dynamic (exponential) delay
|
||||
for mut event in events {
|
||||
event.lives -= 1;
|
||||
event.not_before = Utc::now() + notify_delay(&event);
|
||||
notifyevent_tx.send(event).await?;
|
||||
}
|
||||
},
|
||||
NotifyError::PermanentError(_) => {
|
||||
// Unexpected!
|
||||
// Should be dropped
|
||||
log::error!("None of the configured notification plugins works.");
|
||||
log::error!("Message will be dropped.");
|
||||
// TODO: Send audit/metric information
|
||||
}
|
||||
error => {
|
||||
// Unexpected!
|
||||
log::error!("Internal error: notify_handler returned unexpected error: {:?}", error);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// loop {
|
||||
// tokio::select! {
|
||||
// Some(events) = notifyevent_rx.recv() => {
|
||||
// match notify_handler(¬ify_plugins, &finalize.notification, &events){
|
||||
// Ok(_) => {}, // Notification succeeded / failed with fallible
|
||||
// Err(e) => {
|
||||
// log::debug!("Failed to send notification: {:?}", e);
|
||||
// match e {
|
||||
// NotifyError::PluginNotAvailable(_) => {
|
||||
// // Should keep lives, add static delay and give less priority
|
||||
// for mut event in events {
|
||||
// event.not_before = Utc::now() + NOTIFY_NOTREADY_DELAY;
|
||||
// event.effective_priority = event.effective_priority.saturating_add(1);
|
||||
// notifyevent_tx.send(event).await?;
|
||||
// }
|
||||
// },
|
||||
// NotifyError::TemporaryError(_) => {
|
||||
// // Should cost 1 life, add dynamic (exponential) delay
|
||||
// for mut event in events {
|
||||
// event.lives -= 1;
|
||||
// event.not_before = Utc::now() + notify_delay(&event);
|
||||
// notifyevent_tx.send(event).await?;
|
||||
// }
|
||||
// },
|
||||
// NotifyError::PermanentError(_) => {
|
||||
// // Unexpected!
|
||||
// // Should be dropped
|
||||
// log::error!("None of the configured notification plugins works.");
|
||||
// log::error!("Message will be dropped.");
|
||||
// // TODO: Send audit/metric information
|
||||
// }
|
||||
// error => {
|
||||
// // Unexpected!
|
||||
// log::error!("Internal error: notify_handler returned unexpected error: {:?}", error);
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
todo!();
|
||||
}
|
||||
|
||||
fn notify_handler(
|
||||
fn notification_handler(
|
||||
plugins: &NotifyPluginMap,
|
||||
config: &NotificationConfig,
|
||||
event: &Vec<NotifyEvent>,
|
||||
config: &NotificationRulesMap,
|
||||
event: &BundledNotifyEvent,
|
||||
) -> Result<(), NotifyError> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
use crate::finalize::plugin::FinalizePlugin;
|
||||
use crate::types::buildstatus::StagePhase;
|
||||
use crate::types::buildstatus::StageResult;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use std::hash::Hash;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::finalize::NotificationTemplate;
|
||||
use crate::finalize::config::Trigger;
|
||||
/// Final runtime configuration for notification, restored and flattened from yaml
|
||||
pub struct NotificationRule{
|
||||
pub enabled: bool,
|
||||
|
||||
pub trigger: Vec<Trigger>,
|
||||
|
||||
pub template: NotificationTemplate,
|
||||
|
||||
/// Whether notification failure should not trigger next priority group
|
||||
pub fallible: bool,
|
||||
|
||||
/// Maximum number of attempts
|
||||
pub max_lives: u32,
|
||||
|
||||
/// Timeout for notification execution
|
||||
pub timeout: Duration,
|
||||
|
||||
/// Plugin-specific configuration
|
||||
pub config: serde_yaml::Value,
|
||||
|
||||
/// Factor of exponential backoff for retry delay
|
||||
// Not provided for external use
|
||||
pub retry_backoff: f64,
|
||||
}
|
||||
|
||||
pub type Priority = u32;
|
||||
pub type Method = String;
|
||||
pub type NotificationRulesMap = HashMap<Priority, HashMap<Method, Vec<NotificationRule>>>;
|
||||
pub type BundledNotifyEvent = Vec<NotifyEvent>;
|
||||
pub type NotifyEventReceiver = tokio::sync::mpsc::Receiver<BundledNotifyEvent>;
|
||||
pub type NotifyEventSender = tokio::sync::mpsc::Sender<NotifyEvent>;
|
||||
pub type NotifyPluginMap = HashMap<String, FinalizePlugin>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct NotifyEvent {
|
||||
pub id: Uuid,
|
||||
pub stage: StagePhase,
|
||||
pub substage: String,
|
||||
pub result: StageResult,
|
||||
pub not_before: DateTime<Utc>,
|
||||
pub priority: u32,
|
||||
pub effective_priority: u32,
|
||||
pub lives: u32,
|
||||
pub max_lives: u32,
|
||||
pub target: HashSet<String>, // Target audience
|
||||
}
|
||||
|
||||
impl Hash for NotifyEvent {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NotifyEvent {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for NotifyEvent {}
|
||||
|
||||
impl PartialOrd for NotifyEvent {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(
|
||||
self.effective_priority
|
||||
.cmp(&other.effective_priority)
|
||||
.then(self.not_before.cmp(&other.not_before))
|
||||
.reverse(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for NotifyEvent {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.partial_cmp(other).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub const NOTIFY_NOTREADY_DELAY: std::time::Duration = std::time::Duration::from_secs(5);
|
||||
pub const NOTIFY_TEMPORARY_DELAY_FACTOR: f64 = 2.0;
|
||||
pub const NOTIFY_TEMPORARY_DELAY_TIME: f64 = 5.0;
|
||||
pub const NOTIFY_TEMPORARY_MAX_TIME: f64 = f64::MAX;
|
||||
Reference in New Issue
Block a user